feat(cpp-object-oriented-modular-refactoring): step 4 - model-element-google-style
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#ifndef FESA_ELEMENTS_EULER_BEAM_3D_H_
|
||||
#define FESA_ELEMENTS_EULER_BEAM_3D_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/math/matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
#include "fesa/model/model_types.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Stores constant line-load components in the beam local frame.
|
||||
struct ConstantLocalLineLoad {
|
||||
double px;
|
||||
double py;
|
||||
double pz;
|
||||
double mx;
|
||||
};
|
||||
|
||||
/// @brief Stores one axial stress at a Gauss and section-point identity.
|
||||
struct BeamStressPoint {
|
||||
int gauss_point;
|
||||
std::size_t section_point;
|
||||
double x1;
|
||||
double x2;
|
||||
double s11;
|
||||
std::string source;
|
||||
};
|
||||
|
||||
/// @brief Stores distinct beam end-action, section, Gauss, and stress results.
|
||||
struct BeamRecovery {
|
||||
std::array<std::array<double, 6>, 2> equilibrium_end_actions;
|
||||
std::array<std::array<double, 4>, 2> endpoint_section_resultants;
|
||||
std::array<std::array<double, 4>, 2> gauss_generalized_strains;
|
||||
std::array<std::array<double, 4>, 2> gauss_generalized_resultants;
|
||||
std::vector<BeamStressPoint> stress_points;
|
||||
};
|
||||
|
||||
/// @brief Implements the approved two-node prismatic B33 Euler beam kernel.
|
||||
/// @note Equation numbering and semantic element identity remain external.
|
||||
class EulerBeam3D {
|
||||
public:
|
||||
/// @brief Creates a validated beam kernel and right-handed local frame.
|
||||
/// @param first_node First source node in the element connectivity.
|
||||
/// @param second_node Second source node in the element connectivity.
|
||||
/// @param section Supported general beam section and local first axis.
|
||||
/// @param material Supported isotropic elastic material.
|
||||
/// @return A validated beam or a structured model failure.
|
||||
static Result<EulerBeam3D> Create(const Node& first_node,
|
||||
const Node& second_node,
|
||||
const GeneralBeamSection& section,
|
||||
const LinearElasticMaterial& material);
|
||||
|
||||
/// @brief Computes the 12-by-12 stiffness in local DOF order.
|
||||
/// @note Uses the approved two-point Gauss operation order.
|
||||
Matrix LocalStiffness() const;
|
||||
|
||||
/// @brief Computes the stiffness in stable global element DOF order.
|
||||
Matrix GlobalStiffness() const;
|
||||
|
||||
/// @brief Computes the formulation-only constant local line-load vector.
|
||||
/// @warning This kernel does not expose distributed loads through parser
|
||||
/// input.
|
||||
Vector LocalEquivalentLoad(const ConstantLocalLineLoad& load) const;
|
||||
|
||||
/// @brief Recovers signed physical quantities at their distinct locations.
|
||||
/// @param global_element_displacement Twelve global element DOF values.
|
||||
/// @return Beam recovery rows in deterministic location order.
|
||||
BeamRecovery Recover(const Vector& global_element_displacement) const;
|
||||
|
||||
private:
|
||||
/// @brief Stores already validated geometry, material, and section state.
|
||||
EulerBeam3D(double length, double youngs_modulus, double shear_modulus,
|
||||
double area, double iy, double iz, double torsional_constant,
|
||||
std::array<double, 9> rotation,
|
||||
std::vector<std::array<double, 2>> section_points);
|
||||
|
||||
double length_;
|
||||
double youngs_modulus_;
|
||||
double shear_modulus_;
|
||||
double area_;
|
||||
double iy_;
|
||||
double iz_;
|
||||
double torsional_constant_;
|
||||
std::array<double, 9> rotation_;
|
||||
std::vector<std::array<double, 2>> section_points_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_ELEMENTS_EULER_BEAM_3D_H_
|
||||
@@ -1,74 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/math/matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
#include "fesa/model/model_types.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa {
|
||||
|
||||
struct ConstantLocalLineLoad {
|
||||
double px;
|
||||
double py;
|
||||
double pz;
|
||||
double mx;
|
||||
};
|
||||
|
||||
struct BeamStressPoint {
|
||||
int gaussPoint;
|
||||
std::size_t sectionPoint;
|
||||
double x1;
|
||||
double x2;
|
||||
double s11;
|
||||
std::string source;
|
||||
};
|
||||
|
||||
struct BeamRecovery {
|
||||
std::array<std::array<double, 6>, 2> equilibriumEndActions;
|
||||
std::array<std::array<double, 4>, 2> endpointSectionResultants;
|
||||
std::array<std::array<double, 4>, 2> gaussGeneralizedStrains;
|
||||
std::array<std::array<double, 4>, 2> gaussGeneralizedResultants;
|
||||
std::vector<BeamStressPoint> stressPoints;
|
||||
};
|
||||
|
||||
// Implements the approved two-node straight prismatic B33 Euler-Bernoulli
|
||||
// kernel. Equation numbering and element identity remain outside this type.
|
||||
class EulerBeam3D {
|
||||
public:
|
||||
static Result<EulerBeam3D> create(const Node& firstNode,
|
||||
const Node& secondNode,
|
||||
const GeneralBeamSection& section,
|
||||
const LinearElasticMaterial& material);
|
||||
Matrix localStiffness() const;
|
||||
Matrix globalStiffness() const;
|
||||
Vector localEquivalentLoad(const ConstantLocalLineLoad& load) const;
|
||||
BeamRecovery recover(const Vector& globalElementDisplacement) const;
|
||||
|
||||
private:
|
||||
EulerBeam3D(double length,
|
||||
double youngsModulus,
|
||||
double shearModulus,
|
||||
double area,
|
||||
double iy,
|
||||
double iz,
|
||||
double torsionalConstant,
|
||||
std::array<double, 9> rotation,
|
||||
std::vector<std::array<double, 2>> sectionPoints);
|
||||
|
||||
double length_;
|
||||
double youngsModulus_;
|
||||
double shearModulus_;
|
||||
double area_;
|
||||
double iy_;
|
||||
double iz_;
|
||||
double torsionalConstant_;
|
||||
std::array<double, 9> rotation_;
|
||||
std::vector<std::array<double, 2>> sectionPoints_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
@@ -0,0 +1,179 @@
|
||||
#ifndef FESA_ELEMENTS_MITC4_SHELL_H_
|
||||
#define FESA_ELEMENTS_MITC4_SHELL_H_
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/math/matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
#include "fesa/model/model_types.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Stores bilinear shape values and natural-coordinate derivatives.
|
||||
struct Mitc4ShapeFunctions {
|
||||
std::array<double, 4> values;
|
||||
std::array<double, 4> xi_derivatives;
|
||||
std::array<double, 4> eta_derivatives;
|
||||
};
|
||||
|
||||
/// @brief Stores a right-handed local shell frame at one location.
|
||||
struct Mitc4LocalFrame {
|
||||
std::array<double, 3> e1;
|
||||
std::array<double, 3> e2;
|
||||
std::array<double, 3> e3;
|
||||
};
|
||||
|
||||
/// @brief Stores canonical MITC4 covariant shear interpolation weights.
|
||||
struct Mitc4TyingWeights {
|
||||
std::array<double, 2> xi_zeta;
|
||||
std::array<double, 2> eta_zeta;
|
||||
};
|
||||
|
||||
/// @brief Stores one fixed 2-by-2-by-2 integration point and weight.
|
||||
struct Mitc4QuadraturePoint {
|
||||
std::array<double, 3> natural_coordinates;
|
||||
double weight;
|
||||
};
|
||||
|
||||
/// @brief Separates physical, drilling, and stabilized stiffness matrices.
|
||||
struct Mitc4Stiffness {
|
||||
Matrix physical_local20;
|
||||
Matrix physical_global24;
|
||||
Matrix drilling_global24;
|
||||
Matrix stabilized_global24;
|
||||
double drilling_stiffness;
|
||||
};
|
||||
|
||||
/// @brief Stores physical shell recovery at one midsurface Gauss location.
|
||||
struct Mitc4PhysicalRecoveryPoint {
|
||||
std::array<double, 2> natural_coordinates;
|
||||
Mitc4LocalFrame local_frame;
|
||||
std::array<double, 8> generalized_strain;
|
||||
std::array<double, 8> section_resultant;
|
||||
std::array<std::array<double, 3>, 3> in_plane_stress;
|
||||
};
|
||||
|
||||
/// @brief Stores physical-only recovery rows and strain energy.
|
||||
struct Mitc4PhysicalRecovery {
|
||||
std::array<Mitc4PhysicalRecoveryPoint, 4> points;
|
||||
double strain_energy;
|
||||
};
|
||||
|
||||
/// @brief Implements the approved small-rotation FESA-MITC4 shell kernel.
|
||||
/// @note Physical and numerical drilling contributions remain separate.
|
||||
class Mitc4Shell {
|
||||
public:
|
||||
/// @brief Creates a validated shell kernel from four non-owning node
|
||||
/// pointers.
|
||||
/// @param nodes Node pointers valid for the duration of this call.
|
||||
/// @param initial_directors Validated unit initial directors in node order.
|
||||
/// @param section Centered constant-thickness shell section.
|
||||
/// @param material Supported isotropic elastic material.
|
||||
/// @return A validated shell or a structured model failure.
|
||||
static Result<Mitc4Shell> Create(
|
||||
std::array<const Node*, 4> nodes,
|
||||
std::array<std::array<double, 3>, 4> initial_directors,
|
||||
const ShellSection& section, const LinearElasticMaterial& material);
|
||||
|
||||
/// @brief Evaluates bilinear shape functions and derivatives.
|
||||
static Mitc4ShapeFunctions ShapeFunctions(double xi, double eta) noexcept;
|
||||
|
||||
/// @brief Evaluates the canonical edge-midpoint tying weights.
|
||||
static Mitc4TyingWeights TyingWeights(double xi, double eta) noexcept;
|
||||
|
||||
/// @brief Returns the fixed 2-by-2-by-2 quadrature inventory.
|
||||
static const std::array<Mitc4QuadraturePoint, 8>& VolumeQuadrature() noexcept;
|
||||
|
||||
/// @brief Evaluates the right-handed local frame at a midsurface location.
|
||||
[[nodiscard]] Mitc4LocalFrame LocalFrame(double xi, double eta) const;
|
||||
|
||||
/// @brief Returns the physical 24-to-20 transformation.
|
||||
[[nodiscard]] Matrix PhysicalTransformation20() const;
|
||||
|
||||
/// @brief Returns the numerical drilling 24-to-4 transformation.
|
||||
[[nodiscard]] Matrix DrillingTransformation4() const;
|
||||
|
||||
/// @brief Evaluates the direct five-component physical strain operator.
|
||||
[[nodiscard]] Matrix DirectStrainDisplacement20(double xi, double eta,
|
||||
double zeta) const;
|
||||
|
||||
/// @brief Evaluates all four canonical covariant tying shear samples.
|
||||
[[nodiscard]] Matrix CovariantTyingShearSamples20() const;
|
||||
|
||||
/// @brief Evaluates the MITC-projected five-component strain operator.
|
||||
[[nodiscard]] Matrix StrainDisplacement20(double xi, double eta,
|
||||
double zeta) const;
|
||||
|
||||
/// @brief Returns the isotropic in-plane plane-stress matrix.
|
||||
[[nodiscard]] Matrix PlaneStressConstitutive() const;
|
||||
|
||||
/// @brief Returns the five-component plane-stress and shear matrix.
|
||||
[[nodiscard]] Matrix MaterialConstitutive5() const;
|
||||
|
||||
/// @brief Returns the centered membrane section matrix.
|
||||
[[nodiscard]] Matrix MembraneSectionMatrix() const;
|
||||
|
||||
/// @brief Returns the centered bending section matrix.
|
||||
[[nodiscard]] Matrix BendingSectionMatrix() const;
|
||||
|
||||
/// @brief Returns the corrected transverse-shear section matrix.
|
||||
[[nodiscard]] Matrix TransverseShearSectionMatrix() const;
|
||||
|
||||
/// @brief Computes physical, drilling, and stabilized stiffness matrices.
|
||||
/// @return Finite stiffness matrices or a structured model failure.
|
||||
[[nodiscard]] Result<Mitc4Stiffness> Stiffness() const;
|
||||
|
||||
/// @brief Recovers physical shell quantities without drilling results.
|
||||
/// @param global_element_displacement24 Global element DOFs in node order.
|
||||
/// @return Physical recovery rows or a structured model failure.
|
||||
[[nodiscard]] Result<Mitc4PhysicalRecovery> RecoverPhysical(
|
||||
const Vector& global_element_displacement24) const;
|
||||
|
||||
private:
|
||||
using Vector3 = std::array<double, 3>;
|
||||
|
||||
/// @brief Stores covariant, reciprocal, frame, and Jacobian data at one
|
||||
/// point.
|
||||
struct GeometryData {
|
||||
std::array<Vector3, 3> covariant;
|
||||
std::array<Vector3, 3> reciprocal;
|
||||
Mitc4LocalFrame frame;
|
||||
double jacobian;
|
||||
};
|
||||
|
||||
/// @brief Stores validated shell geometry and constitutive state.
|
||||
Mitc4Shell(std::array<Vector3, 4> coordinates,
|
||||
std::array<Vector3, 4> directors, std::array<Vector3, 4> tangent_a,
|
||||
std::array<Vector3, 4> tangent_b, Vector3 normal_candidate,
|
||||
double thickness, double youngs_modulus, double poisson_ratio,
|
||||
SourceLocation source_location, std::string identity);
|
||||
|
||||
/// @brief Evaluates a finite positive Jacobian and right-handed frame.
|
||||
bool EvaluateGeometry(double xi, double eta, double zeta,
|
||||
GeometryData& result) const noexcept;
|
||||
|
||||
/// @brief Evaluates displacement-basis derivatives in covariant directions.
|
||||
std::array<std::array<Vector3, 3>, 20> BasisDerivatives(
|
||||
double xi, double eta, double zeta) const noexcept;
|
||||
|
||||
/// @brief Builds direct or tied strain without changing projection order.
|
||||
Matrix StrainDisplacement(double xi, double eta, double zeta,
|
||||
const Matrix* tying_samples) const;
|
||||
|
||||
std::array<Vector3, 4> coordinates_;
|
||||
std::array<Vector3, 4> directors_;
|
||||
std::array<Vector3, 4> tangent_a_;
|
||||
std::array<Vector3, 4> tangent_b_;
|
||||
Vector3 normal_candidate_;
|
||||
double thickness_;
|
||||
double youngs_modulus_;
|
||||
double poisson_ratio_;
|
||||
SourceLocation source_location_;
|
||||
std::string identity_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_ELEMENTS_MITC4_SHELL_H_
|
||||
@@ -1,142 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/math/matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
#include "fesa/model/model_types.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace fesa {
|
||||
|
||||
struct Mitc4ShapeFunctions {
|
||||
std::array<double, 4> values;
|
||||
std::array<double, 4> xiDerivatives;
|
||||
std::array<double, 4> etaDerivatives;
|
||||
};
|
||||
|
||||
struct Mitc4LocalFrame {
|
||||
std::array<double, 3> e1;
|
||||
std::array<double, 3> e2;
|
||||
std::array<double, 3> e3;
|
||||
};
|
||||
|
||||
struct Mitc4TyingWeights {
|
||||
std::array<double, 2> xiZeta;
|
||||
std::array<double, 2> etaZeta;
|
||||
};
|
||||
|
||||
struct Mitc4QuadraturePoint {
|
||||
std::array<double, 3> naturalCoordinates;
|
||||
double weight;
|
||||
};
|
||||
|
||||
struct Mitc4Stiffness {
|
||||
Matrix physicalLocal20;
|
||||
Matrix physicalGlobal24;
|
||||
Matrix drillingGlobal24;
|
||||
Matrix stabilizedGlobal24;
|
||||
double drillingStiffness;
|
||||
};
|
||||
|
||||
struct Mitc4PhysicalRecoveryPoint {
|
||||
std::array<double, 2> naturalCoordinates;
|
||||
Mitc4LocalFrame localFrame;
|
||||
std::array<double, 8> generalizedStrain;
|
||||
std::array<double, 8> sectionResultant;
|
||||
std::array<std::array<double, 3>, 3> inPlaneStress;
|
||||
};
|
||||
|
||||
struct Mitc4PhysicalRecovery {
|
||||
std::array<Mitc4PhysicalRecoveryPoint, 4> points;
|
||||
double strainEnergy;
|
||||
};
|
||||
|
||||
// Concrete small-rotation MITC4 kinematics, constitutive, stiffness, and
|
||||
// physical-only recovery kernel. Global equation/result ownership remains outside.
|
||||
class Mitc4Shell {
|
||||
public:
|
||||
static Result<Mitc4Shell> create(
|
||||
std::array<const Node*, 4> nodes,
|
||||
std::array<std::array<double, 3>, 4> initialDirectors,
|
||||
const ShellSection& section,
|
||||
const LinearElasticMaterial& material);
|
||||
|
||||
static Mitc4ShapeFunctions shapeFunctions(double xi, double eta) noexcept;
|
||||
static Mitc4TyingWeights tyingWeights(double xi, double eta) noexcept;
|
||||
static const std::array<Mitc4QuadraturePoint, 8>&
|
||||
volumeQuadrature() noexcept;
|
||||
|
||||
[[nodiscard]] Mitc4LocalFrame localFrame(double xi, double eta) const;
|
||||
[[nodiscard]] Matrix physicalTransformation20() const;
|
||||
[[nodiscard]] Matrix drillingTransformation4() const;
|
||||
[[nodiscard]] Matrix directStrainDisplacement20(
|
||||
double xi,
|
||||
double eta,
|
||||
double zeta) const;
|
||||
[[nodiscard]] Matrix covariantTyingShearSamples20() const;
|
||||
[[nodiscard]] Matrix strainDisplacement20(
|
||||
double xi,
|
||||
double eta,
|
||||
double zeta) const;
|
||||
|
||||
[[nodiscard]] Matrix planeStressConstitutive() const;
|
||||
[[nodiscard]] Matrix materialConstitutive5() const;
|
||||
[[nodiscard]] Matrix membraneSectionMatrix() const;
|
||||
[[nodiscard]] Matrix bendingSectionMatrix() const;
|
||||
[[nodiscard]] Matrix transverseShearSectionMatrix() const;
|
||||
[[nodiscard]] Result<Mitc4Stiffness> stiffness() const;
|
||||
[[nodiscard]] Result<Mitc4PhysicalRecovery> recoverPhysical(
|
||||
const Vector& globalElementDisplacement24) const;
|
||||
|
||||
private:
|
||||
using Vector3 = std::array<double, 3>;
|
||||
|
||||
struct GeometryData {
|
||||
std::array<Vector3, 3> covariant;
|
||||
std::array<Vector3, 3> reciprocal;
|
||||
Mitc4LocalFrame frame;
|
||||
double jacobian;
|
||||
};
|
||||
|
||||
Mitc4Shell(
|
||||
std::array<Vector3, 4> coordinates,
|
||||
std::array<Vector3, 4> directors,
|
||||
std::array<Vector3, 4> tangentA,
|
||||
std::array<Vector3, 4> tangentB,
|
||||
Vector3 normalCandidate,
|
||||
double thickness,
|
||||
double youngsModulus,
|
||||
double poissonRatio,
|
||||
SourceLocation sourceLocation,
|
||||
std::string identity);
|
||||
|
||||
bool evaluateGeometry(
|
||||
double xi,
|
||||
double eta,
|
||||
double zeta,
|
||||
GeometryData& result) const noexcept;
|
||||
std::array<std::array<Vector3, 3>, 20> basisDerivatives(
|
||||
double xi,
|
||||
double eta,
|
||||
double zeta) const noexcept;
|
||||
Matrix strainDisplacement(
|
||||
double xi,
|
||||
double eta,
|
||||
double zeta,
|
||||
const Matrix* tyingSamples) const;
|
||||
|
||||
std::array<Vector3, 4> coordinates_;
|
||||
std::array<Vector3, 4> directors_;
|
||||
std::array<Vector3, 4> tangentA_;
|
||||
std::array<Vector3, 4> tangentB_;
|
||||
Vector3 normalCandidate_;
|
||||
double thickness_;
|
||||
double youngsModulus_;
|
||||
double poissonRatio_;
|
||||
SourceLocation sourceLocation_;
|
||||
std::string identity_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#ifndef FESA_MODEL_DOMAIN_H_
|
||||
#define FESA_MODEL_DOMAIN_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/model/model_types.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Owns the complete immutable semantic model definition.
|
||||
/// @note Collection positions remain stable internal indices after
|
||||
/// construction.
|
||||
class Domain {
|
||||
public:
|
||||
/// @brief Creates a Domain that owns a copy or moved model definition.
|
||||
/// @param definition Complete parsed semantic records in declaration order.
|
||||
/// @return A successful owning Domain.
|
||||
static Result<Domain> Create(ModelDefinition definition);
|
||||
|
||||
/// @brief Returns nodes in stable declaration order.
|
||||
const std::vector<Node>& Nodes() const noexcept;
|
||||
|
||||
/// @brief Returns Euler beam definitions in stable declaration order.
|
||||
const std::vector<EulerBeam3DDefinition>& Elements() const noexcept;
|
||||
|
||||
/// @brief Returns MITC4 shell definitions in stable declaration order.
|
||||
const std::vector<Mitc4ShellDefinition>& ShellElements() const noexcept;
|
||||
|
||||
/// @brief Returns materials in stable declaration order.
|
||||
const std::vector<LinearElasticMaterial>& Materials() const noexcept;
|
||||
|
||||
/// @brief Returns beam sections in stable declaration order.
|
||||
const std::vector<GeneralBeamSection>& Sections() const noexcept;
|
||||
|
||||
/// @brief Returns shell sections in stable declaration order.
|
||||
const std::vector<ShellSection>& ShellSections() const noexcept;
|
||||
|
||||
/// @brief Returns preprocessed shell-node frames in stable node order.
|
||||
const std::vector<ShellNodeInitialFrame>& ShellNodeInitialFrames()
|
||||
const noexcept;
|
||||
|
||||
/// @brief Returns node sets in stable declaration order.
|
||||
const std::vector<NodeSet>& NodeSets() const noexcept;
|
||||
|
||||
/// @brief Returns element sets in stable declaration order.
|
||||
const std::vector<ElementSet>& ElementSets() const noexcept;
|
||||
|
||||
/// @brief Returns static steps in stable declaration order.
|
||||
const std::vector<StaticStepDefinition>& Steps() const noexcept;
|
||||
|
||||
/// @brief Returns sorted nonfatal mapping diagnostics.
|
||||
const std::vector<Diagnostic>& Warnings() const noexcept;
|
||||
|
||||
/// @brief Returns the source input path associated with this model.
|
||||
const std::filesystem::path& SourcePath() const noexcept;
|
||||
|
||||
/// @brief Returns the deterministic source-content identity.
|
||||
const std::string& SourceContentIdentity() const noexcept;
|
||||
|
||||
private:
|
||||
/// @brief Takes ownership of an already constructed model definition.
|
||||
explicit Domain(ModelDefinition definition);
|
||||
|
||||
ModelDefinition definition_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_MODEL_DOMAIN_H_
|
||||
@@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/model/model_types.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa {
|
||||
|
||||
// Owns the complete semantic definition. Public access remains const so a
|
||||
// vector position can serve as a stable internal index after construction.
|
||||
class Domain {
|
||||
public:
|
||||
static Result<Domain> create(ModelDefinition definition);
|
||||
|
||||
const std::vector<Node>& nodes() const noexcept;
|
||||
const std::vector<EulerBeam3DDefinition>& elements() const noexcept;
|
||||
const std::vector<Mitc4ShellDefinition>& shellElements() const noexcept;
|
||||
const std::vector<LinearElasticMaterial>& materials() const noexcept;
|
||||
const std::vector<GeneralBeamSection>& sections() const noexcept;
|
||||
const std::vector<ShellSection>& shellSections() const noexcept;
|
||||
const std::vector<ShellNodeInitialFrame>& shellNodeInitialFrames() const noexcept;
|
||||
const std::vector<NodeSet>& nodeSets() const noexcept;
|
||||
const std::vector<ElementSet>& elementSets() const noexcept;
|
||||
const std::vector<StaticStepDefinition>& steps() const noexcept;
|
||||
const std::vector<Diagnostic>& warnings() const noexcept;
|
||||
const std::filesystem::path& sourcePath() const noexcept;
|
||||
const std::string& sourceContentIdentity() const noexcept;
|
||||
|
||||
private:
|
||||
explicit Domain(ModelDefinition definition);
|
||||
|
||||
ModelDefinition definition_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
@@ -0,0 +1,185 @@
|
||||
#ifndef FESA_MODEL_MODEL_TYPES_H_
|
||||
#define FESA_MODEL_MODEL_TYPES_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/core/source_identity.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Identifies a semantic entity by its stable collection position.
|
||||
using EntityIndex = std::uint32_t;
|
||||
|
||||
/// @brief Stores one source node and its global coordinates.
|
||||
struct Node {
|
||||
SourceEntityId source_id;
|
||||
std::array<double, 3> coordinates;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Stores the supported homogeneous isotropic elastic properties.
|
||||
struct LinearElasticMaterial {
|
||||
std::string name;
|
||||
double youngs_modulus;
|
||||
double poisson_ratio;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Stores the supported general Euler beam section properties.
|
||||
struct GeneralBeamSection {
|
||||
std::string name;
|
||||
double area;
|
||||
double i11;
|
||||
double i12;
|
||||
double i22;
|
||||
double torsional_constant;
|
||||
std::array<double, 3> first_axis;
|
||||
std::vector<std::array<double, 2>> section_points;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Preserves the source shell element label independently of
|
||||
/// formulation.
|
||||
enum class ShellSourceElementType { kS4, kS4r };
|
||||
|
||||
/// @brief Names the single internal shell formulation selected by S4 and S4R.
|
||||
inline constexpr std::string_view kMitc4InternalFormulation{"FESA-MITC4"};
|
||||
|
||||
/// @brief Stores a centered constant-thickness shell section assignment.
|
||||
struct ShellSection {
|
||||
std::string name;
|
||||
double thickness;
|
||||
EntityIndex material_index;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Defines one four-node shell with stable semantic references.
|
||||
struct Mitc4ShellDefinition {
|
||||
SourceEntityId source_id;
|
||||
ShellSourceElementType source_type;
|
||||
std::array<EntityIndex, 4> node_indices;
|
||||
EntityIndex material_index;
|
||||
EntityIndex section_index;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Stores the deterministic initial director and tangent frame at a
|
||||
/// node.
|
||||
struct ShellNodeInitialFrame {
|
||||
EntityIndex node_index;
|
||||
std::array<double, 3> director;
|
||||
std::array<double, 3> tangent_a;
|
||||
std::array<double, 3> tangent_b;
|
||||
};
|
||||
|
||||
/// @brief Defines one two-node Euler beam with stable semantic references.
|
||||
struct EulerBeam3DDefinition {
|
||||
SourceEntityId source_id;
|
||||
std::array<EntityIndex, 2> node_indices;
|
||||
EntityIndex material_index;
|
||||
EntityIndex section_index;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Stores one prescribed nodal degree-of-freedom range.
|
||||
struct BoundaryCondition {
|
||||
std::string target;
|
||||
int first_dof;
|
||||
int last_dof;
|
||||
double value;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Stores one concentrated nodal load component.
|
||||
struct NodalLoad {
|
||||
std::string target;
|
||||
int dof;
|
||||
double magnitude;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Stores the approved single linear-static step definition.
|
||||
struct StaticStepDefinition {
|
||||
std::string name;
|
||||
std::vector<BoundaryCondition> boundaries;
|
||||
std::vector<NodalLoad> loads;
|
||||
double initial_increment;
|
||||
double time_period;
|
||||
double minimum_increment;
|
||||
double maximum_increment;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Stores a stable resolved node-set membership list.
|
||||
struct NodeSet {
|
||||
std::string name;
|
||||
std::optional<std::string> instance_name;
|
||||
std::vector<EntityIndex> node_indices;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Stores a stable resolved element-set membership list.
|
||||
struct ElementSet {
|
||||
std::string name;
|
||||
std::optional<std::string> instance_name;
|
||||
std::vector<EntityIndex> element_indices;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Preserves source identities declared inside one part.
|
||||
struct PartDefinition {
|
||||
std::string name;
|
||||
std::vector<std::int64_t> node_source_labels;
|
||||
std::vector<std::int64_t> element_source_labels;
|
||||
std::vector<std::string> node_set_names;
|
||||
std::vector<std::string> element_set_names;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Maps one source label to a stable internal entity index.
|
||||
struct SourceIndexMapping {
|
||||
std::int64_t source_label;
|
||||
EntityIndex internal_index;
|
||||
};
|
||||
|
||||
/// @brief Preserves one identity instance and its deterministic source
|
||||
/// mappings.
|
||||
struct InstanceDefinition {
|
||||
std::string name;
|
||||
std::string part_name;
|
||||
std::vector<SourceIndexMapping> node_mappings;
|
||||
std::vector<SourceIndexMapping> element_mappings;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Owns every parsed semantic record before immutable Domain
|
||||
/// construction.
|
||||
struct ModelDefinition {
|
||||
std::filesystem::path source_path;
|
||||
std::string source_content_identity;
|
||||
std::string heading;
|
||||
std::vector<Node> nodes;
|
||||
std::vector<EulerBeam3DDefinition> elements;
|
||||
std::vector<Mitc4ShellDefinition> shell_elements;
|
||||
std::vector<LinearElasticMaterial> materials;
|
||||
std::vector<GeneralBeamSection> sections;
|
||||
std::vector<ShellSection> shell_sections;
|
||||
std::vector<ShellNodeInitialFrame> shell_node_initial_frames;
|
||||
std::vector<NodeSet> node_sets;
|
||||
std::vector<ElementSet> element_sets;
|
||||
std::vector<PartDefinition> parts;
|
||||
std::vector<InstanceDefinition> instances;
|
||||
std::vector<StaticStepDefinition> steps;
|
||||
std::vector<Diagnostic> warnings;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_MODEL_MODEL_TYPES_H_
|
||||
@@ -1,165 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/core/source_identity.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa {
|
||||
|
||||
// Stable internal identities are vector positions assigned in declaration order.
|
||||
using EntityIndex = std::uint32_t;
|
||||
|
||||
struct Node {
|
||||
SourceEntityId sourceId;
|
||||
std::array<double, 3> coordinates;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct LinearElasticMaterial {
|
||||
std::string name;
|
||||
double youngsModulus;
|
||||
double poissonRatio;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct GeneralBeamSection {
|
||||
std::string name;
|
||||
double area;
|
||||
double i11;
|
||||
double i12;
|
||||
double i22;
|
||||
double torsionalConstant;
|
||||
std::array<double, 3> firstAxis;
|
||||
std::vector<std::array<double, 2>> sectionPoints;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
enum class ShellSourceElementType {
|
||||
s4,
|
||||
s4r
|
||||
};
|
||||
|
||||
inline constexpr std::string_view kMitc4InternalFormulation{"FESA-MITC4"};
|
||||
|
||||
struct ShellSection {
|
||||
std::string name;
|
||||
double thickness;
|
||||
EntityIndex materialIndex;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct Mitc4ShellDefinition {
|
||||
SourceEntityId sourceId;
|
||||
ShellSourceElementType sourceType;
|
||||
std::array<EntityIndex, 4> nodeIndices;
|
||||
EntityIndex materialIndex;
|
||||
EntityIndex sectionIndex;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct ShellNodeInitialFrame {
|
||||
EntityIndex nodeIndex;
|
||||
std::array<double, 3> director;
|
||||
std::array<double, 3> tangentA;
|
||||
std::array<double, 3> tangentB;
|
||||
};
|
||||
|
||||
struct EulerBeam3DDefinition {
|
||||
SourceEntityId sourceId;
|
||||
std::array<EntityIndex, 2> nodeIndices;
|
||||
EntityIndex materialIndex;
|
||||
EntityIndex sectionIndex;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct BoundaryCondition {
|
||||
std::string target;
|
||||
int firstDof;
|
||||
int lastDof;
|
||||
double value;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct NodalLoad {
|
||||
std::string target;
|
||||
int dof;
|
||||
double magnitude;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct StaticStepDefinition {
|
||||
std::string name;
|
||||
std::vector<BoundaryCondition> boundaries;
|
||||
std::vector<NodalLoad> loads;
|
||||
double initialIncrement;
|
||||
double timePeriod;
|
||||
double minimumIncrement;
|
||||
double maximumIncrement;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct NodeSet {
|
||||
std::string name;
|
||||
std::optional<std::string> instanceName;
|
||||
std::vector<EntityIndex> nodeIndices;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct ElementSet {
|
||||
std::string name;
|
||||
std::optional<std::string> instanceName;
|
||||
std::vector<EntityIndex> elementIndices;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct PartDefinition {
|
||||
std::string name;
|
||||
std::vector<std::int64_t> nodeSourceLabels;
|
||||
std::vector<std::int64_t> elementSourceLabels;
|
||||
std::vector<std::string> nodeSetNames;
|
||||
std::vector<std::string> elementSetNames;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct SourceIndexMapping {
|
||||
std::int64_t sourceLabel;
|
||||
EntityIndex internalIndex;
|
||||
};
|
||||
|
||||
struct InstanceDefinition {
|
||||
std::string name;
|
||||
std::string partName;
|
||||
std::vector<SourceIndexMapping> nodeMappings;
|
||||
std::vector<SourceIndexMapping> elementMappings;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
// This construction-boundary value owns every parsed semantic record before
|
||||
// it is finalized into an immutable Domain.
|
||||
struct ModelDefinition {
|
||||
std::filesystem::path sourcePath;
|
||||
std::string sourceContentIdentity;
|
||||
std::string heading;
|
||||
std::vector<Node> nodes;
|
||||
std::vector<EulerBeam3DDefinition> elements;
|
||||
std::vector<Mitc4ShellDefinition> shellElements;
|
||||
std::vector<LinearElasticMaterial> materials;
|
||||
std::vector<GeneralBeamSection> sections;
|
||||
std::vector<ShellSection> shellSections;
|
||||
std::vector<ShellNodeInitialFrame> shellNodeInitialFrames;
|
||||
std::vector<NodeSet> nodeSets;
|
||||
std::vector<ElementSet> elementSets;
|
||||
std::vector<PartDefinition> parts;
|
||||
std::vector<InstanceDefinition> instances;
|
||||
std::vector<StaticStepDefinition> steps;
|
||||
std::vector<Diagnostic> warnings;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef FESA_MODEL_SHELL_GEOMETRY_H_
|
||||
#define FESA_MODEL_SHELL_GEOMETRY_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/model/model_types.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Classifies a mandatory shell-geometry validation location.
|
||||
enum class ShellGeometryPointKind { kCenter, kStiffness, kTying, kRecovery };
|
||||
|
||||
/// @brief Identifies one deterministic shell-geometry validation point.
|
||||
struct ShellGeometryValidationPoint {
|
||||
ShellGeometryPointKind kind;
|
||||
std::size_t location_index;
|
||||
std::array<double, 3> natural_coordinates;
|
||||
};
|
||||
|
||||
/// @brief Stores deterministic preprocessing data for one shell element.
|
||||
struct ShellElementGeometryData {
|
||||
EntityIndex element_index;
|
||||
std::array<double, 3> normal_candidate;
|
||||
double surface_area_weight;
|
||||
};
|
||||
|
||||
/// @brief Owns preprocessed shell node frames and element geometry data.
|
||||
struct ShellGeometry {
|
||||
std::vector<ShellNodeInitialFrame> nodal_frames;
|
||||
std::vector<ShellElementGeometryData> element_data;
|
||||
};
|
||||
|
||||
/// @brief Returns the complete fixed validation-point inventory.
|
||||
/// @note Ordering is center, stiffness, tying, then recovery identity.
|
||||
const std::array<ShellGeometryValidationPoint, 17>&
|
||||
ShellGeometryValidationPoints() noexcept;
|
||||
|
||||
/// @brief Builds deterministic nodal frames and validates shell geometry.
|
||||
/// @param nodes Source nodes indexed by stable EntityIndex.
|
||||
/// @param elements Shell definitions in stable source order.
|
||||
/// @param sections Shell sections used for thickness validation.
|
||||
/// @return Validated geometry or a structured model failure.
|
||||
Result<ShellGeometry> PreprocessShellGeometry(
|
||||
const std::vector<Node>& nodes,
|
||||
const std::vector<Mitc4ShellDefinition>& elements,
|
||||
const std::vector<ShellSection>& sections);
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_MODEL_SHELL_GEOMETRY_H_
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/model/model_types.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa {
|
||||
|
||||
enum class ShellGeometryPointKind {
|
||||
center,
|
||||
stiffness,
|
||||
tying,
|
||||
recovery
|
||||
};
|
||||
|
||||
struct ShellGeometryValidationPoint {
|
||||
ShellGeometryPointKind kind;
|
||||
std::size_t locationIndex;
|
||||
std::array<double, 3> naturalCoordinates;
|
||||
};
|
||||
|
||||
struct ShellElementGeometryData {
|
||||
EntityIndex elementIndex;
|
||||
std::array<double, 3> normalCandidate;
|
||||
double surfaceAreaWeight;
|
||||
};
|
||||
|
||||
struct ShellGeometry {
|
||||
std::vector<ShellNodeInitialFrame> nodalFrames;
|
||||
std::vector<ShellElementGeometryData> elementData;
|
||||
};
|
||||
|
||||
const std::array<ShellGeometryValidationPoint, 17>&
|
||||
shellGeometryValidationPoints() noexcept;
|
||||
|
||||
Result<ShellGeometry> preprocessShellGeometry(
|
||||
const std::vector<Node>& nodes,
|
||||
const std::vector<Mitc4ShellDefinition>& elements,
|
||||
const std::vector<ShellSection>& sections);
|
||||
|
||||
} // namespace fesa
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/model/model_types.hpp"
|
||||
#include "fesa/model/model_types.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "fesa/analysis/analysis_state.hpp"
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
namespace fesa {
|
||||
|
||||
Result<AnalysisModel> AnalysisModel::create(const Domain& domain) {
|
||||
if (domain.steps().empty()) {
|
||||
if (domain.Steps().empty()) {
|
||||
return Result<AnalysisModel>::Failure(Status::Failure(
|
||||
FailureCategory::kInput,
|
||||
{{Severity::kError,
|
||||
"invalid-model-cardinality",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
"STEP",
|
||||
"0",
|
||||
"AnalysisModel requires exactly one static step."}}));
|
||||
}
|
||||
if (domain.steps().size() > 1U) {
|
||||
const auto& secondStep = domain.steps()[1];
|
||||
if (domain.Steps().size() > 1U) {
|
||||
const auto& secondStep = domain.Steps()[1];
|
||||
return Result<AnalysisModel>::Failure(Status::Failure(
|
||||
FailureCategory::kInput,
|
||||
{{Severity::kError,
|
||||
@@ -35,7 +35,7 @@ const Domain& AnalysisModel::domain() const noexcept {
|
||||
}
|
||||
|
||||
const StaticStepDefinition& AnalysisModel::step() const noexcept {
|
||||
return domain_->steps().front();
|
||||
return domain_->Steps().front();
|
||||
}
|
||||
|
||||
const std::vector<EntityIndex>& AnalysisModel::activeElements() const noexcept {
|
||||
@@ -60,14 +60,14 @@ const std::vector<EntityIndex>& AnalysisModel::activeLoads() const noexcept {
|
||||
}
|
||||
|
||||
AnalysisModel::AnalysisModel(const Domain& domain) : domain_{&domain} {
|
||||
std::vector<bool> reachableMaterials(domain.materials().size(), false);
|
||||
std::vector<bool> reachableSections(domain.sections().size(), false);
|
||||
std::vector<bool> reachableMaterials(domain.Materials().size(), false);
|
||||
std::vector<bool> reachableSections(domain.Sections().size(), false);
|
||||
|
||||
for (std::size_t index = 0U; index < domain.elements().size(); ++index) {
|
||||
const auto& element = domain.elements()[index];
|
||||
for (std::size_t index = 0U; index < domain.Elements().size(); ++index) {
|
||||
const auto& element = domain.Elements()[index];
|
||||
activeElements_.push_back(static_cast<EntityIndex>(index));
|
||||
reachableMaterials[element.materialIndex] = true;
|
||||
reachableSections[element.sectionIndex] = true;
|
||||
reachableMaterials[element.material_index] = true;
|
||||
reachableSections[element.section_index] = true;
|
||||
}
|
||||
|
||||
// Ascending vector positions are the stable internal order, independent
|
||||
|
||||
@@ -76,7 +76,7 @@ Status LinearStaticAnalysis::initialize(const AnalysisRequest& request) {
|
||||
}
|
||||
|
||||
domain_ = std::make_unique<Domain>(std::move(domain.Value()));
|
||||
diagnostics_ = domain_->warnings();
|
||||
diagnostics_ = domain_->Warnings();
|
||||
SortDiagnostics(diagnostics_);
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ Result<std::vector<EntityIndex>> resolveTarget(
|
||||
const Domain& domain,
|
||||
const NodalLoad& load) {
|
||||
std::vector<const NodeSet*> matchingSets;
|
||||
for (const auto& set : domain.nodeSets()) {
|
||||
for (const auto& set : domain.NodeSets()) {
|
||||
if (equalName(set.name, load.target)) {
|
||||
matchingSets.push_back(&set);
|
||||
}
|
||||
@@ -155,8 +155,8 @@ Result<std::vector<EntityIndex>> resolveTarget(
|
||||
std::vector<EntityIndex> matchingNodes;
|
||||
std::int64_t label = 0;
|
||||
if (tryPositiveInteger(load.target, label)) {
|
||||
for (std::size_t index = 0U; index < domain.nodes().size(); ++index) {
|
||||
if (domain.nodes()[index].sourceId.source_label == label) {
|
||||
for (std::size_t index = 0U; index < domain.Nodes().size(); ++index) {
|
||||
if (domain.Nodes()[index].source_id.source_label == label) {
|
||||
matchingNodes.push_back(static_cast<EntityIndex>(index));
|
||||
}
|
||||
}
|
||||
@@ -172,10 +172,10 @@ Result<std::vector<EntityIndex>> resolveTarget(
|
||||
"The load target must resolve unambiguously to one node or one expanded node set."));
|
||||
}
|
||||
if (!matchingSets.empty()) {
|
||||
const auto& nodes = matchingSets.front()->nodeIndices;
|
||||
std::vector<unsigned char> seen(domain.nodes().size(), 0U);
|
||||
const auto& nodes = matchingSets.front()->node_indices;
|
||||
std::vector<unsigned char> seen(domain.Nodes().size(), 0U);
|
||||
for (const EntityIndex node : nodes) {
|
||||
if (node >= domain.nodes().size() || seen[node] != 0U) {
|
||||
if (node >= domain.Nodes().size() || seen[node] != 0U) {
|
||||
return Result<std::vector<EntityIndex>>::Failure(loadFailure(
|
||||
"invalid-load-target",
|
||||
load.location,
|
||||
@@ -219,26 +219,26 @@ Status validateFiniteVector(
|
||||
Status validateShellMoments(
|
||||
const Domain& domain,
|
||||
const Vector& fullLoad) {
|
||||
if (domain.shellElements().empty()) {
|
||||
if (domain.ShellElements().empty()) {
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
std::vector<const ShellNodeInitialFrame*> frameByNode(
|
||||
domain.nodes().size(), nullptr);
|
||||
for (const auto& frame : domain.shellNodeInitialFrames()) {
|
||||
if (frame.nodeIndex >= frameByNode.size() ||
|
||||
frameByNode[frame.nodeIndex] != nullptr) {
|
||||
domain.Nodes().size(), nullptr);
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
if (frame.node_index >= frameByNode.size() ||
|
||||
frameByNode[frame.node_index] != nullptr) {
|
||||
return loadFailure(
|
||||
"invalid-shell-director",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
"NODE",
|
||||
std::to_string(frame.nodeIndex),
|
||||
std::to_string(frame.node_index),
|
||||
"Shell nodal directors must have unique in-range node identities.");
|
||||
}
|
||||
frameByNode[frame.nodeIndex] = &frame;
|
||||
frameByNode[frame.node_index] = &frame;
|
||||
}
|
||||
|
||||
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
|
||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||
const double momentX = fullLoad[node * dofsPerNode + 3U];
|
||||
const double momentY = fullLoad[node * dofsPerNode + 4U];
|
||||
const double momentZ = fullLoad[node * dofsPerNode + 5U];
|
||||
@@ -250,9 +250,9 @@ Status validateShellMoments(
|
||||
if (frame == nullptr) {
|
||||
return loadFailure(
|
||||
"invalid-shell-director",
|
||||
domain.nodes()[node].location,
|
||||
domain.Nodes()[node].location,
|
||||
"NODE",
|
||||
domain.nodes()[node].sourceId.source_label_text,
|
||||
domain.Nodes()[node].source_id.source_label_text,
|
||||
"A loaded shell node must have an approved initial director.");
|
||||
}
|
||||
|
||||
@@ -271,9 +271,9 @@ Status validateShellMoments(
|
||||
if (!(projectionRatio <= shellMomentProjectionTolerance)) {
|
||||
return loadFailure(
|
||||
"unsupported-drilling-load",
|
||||
domain.nodes()[node].location,
|
||||
domain.Nodes()[node].location,
|
||||
"CLOAD",
|
||||
domain.nodes()[node].sourceId.source_label_text,
|
||||
domain.Nodes()[node].source_id.source_label_text,
|
||||
"The aggregate nodal moment has an unsupported director-parallel component.");
|
||||
}
|
||||
}
|
||||
@@ -286,23 +286,23 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
|
||||
const AnalysisModel& model,
|
||||
const DofManager& dofs) {
|
||||
const Domain& domain = model.domain();
|
||||
if (domain.nodes().size() >
|
||||
if (domain.Nodes().size() >
|
||||
(std::numeric_limits<std::size_t>::max)() / dofsPerNode) {
|
||||
return Result<Vector>::Failure(loadFailure(
|
||||
"invalid-load-dimensions",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
"LOAD_ASSEMBLER",
|
||||
domain.sourceContentIdentity(),
|
||||
domain.SourceContentIdentity(),
|
||||
"The semantic node count cannot be represented in full-DOF order."));
|
||||
}
|
||||
const std::size_t expectedFullCount =
|
||||
domain.nodes().size() * dofsPerNode;
|
||||
domain.Nodes().size() * dofsPerNode;
|
||||
const Status dofStatus = validateDofOrder(
|
||||
dofs, expectedFullCount, {domain.sourcePath(), 0U});
|
||||
dofs, expectedFullCount, {domain.SourcePath(), 0U});
|
||||
if (!dofStatus.IsOk()) {
|
||||
return Result<Vector>::Failure(dofStatus);
|
||||
}
|
||||
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
|
||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||
for (std::size_t component = 0U;
|
||||
component < dofsPerNode;
|
||||
++component) {
|
||||
@@ -313,17 +313,17 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
|
||||
node * dofsPerNode + component) {
|
||||
return Result<Vector>::Failure(loadFailure(
|
||||
"invalid-load-order",
|
||||
domain.nodes()[node].location,
|
||||
domain.Nodes()[node].location,
|
||||
"LOAD_ASSEMBLER",
|
||||
domain.nodes()[node].sourceId.source_label_text,
|
||||
domain.Nodes()[node].source_id.source_label_text,
|
||||
"DofManager node/component identity must match full-DOF order."));
|
||||
}
|
||||
} catch (const std::out_of_range&) {
|
||||
return Result<Vector>::Failure(loadFailure(
|
||||
"invalid-load-dimensions",
|
||||
domain.nodes()[node].location,
|
||||
domain.Nodes()[node].location,
|
||||
"LOAD_ASSEMBLER",
|
||||
domain.nodes()[node].sourceId.source_label_text,
|
||||
domain.Nodes()[node].source_id.source_label_text,
|
||||
"DofManager must provide all six DOFs for every semantic node."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include "fesa/analysis/analysis_model.hpp"
|
||||
#include "fesa/assembly/parallel_for.hpp"
|
||||
#include "fesa/elements/euler_beam_3d.hpp"
|
||||
#include "fesa/elements/mitc4_shell.hpp"
|
||||
#include "fesa/elements/euler_beam_3d.h"
|
||||
#include "fesa/elements/mitc4_shell.h"
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
|
||||
#include <array>
|
||||
@@ -52,46 +52,46 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
const DofManager& dofs,
|
||||
const ParallelFor& parallelFor) {
|
||||
const Domain& domain = model.domain();
|
||||
if (domain.nodes().size() >
|
||||
if (domain.Nodes().size() >
|
||||
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode ||
|
||||
dofs.fullDofCount() != domain.nodes().size() * kDofsPerNode) {
|
||||
dofs.fullDofCount() != domain.Nodes().size() * kDofsPerNode) {
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-dimensions",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(dofs.fullDofCount()),
|
||||
"DofManager dimensions do not match the active model nodes.");
|
||||
}
|
||||
if (!model.activeElements().empty() && !domain.shellElements().empty()) {
|
||||
if (!model.activeElements().empty() && !domain.ShellElements().empty()) {
|
||||
return assemblyFailure(
|
||||
"unsupported-mixed-element-model",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
"B33:FESA-MITC4",
|
||||
"Sparse assembly does not support mixed beam and shell models.");
|
||||
}
|
||||
|
||||
if (!domain.shellElements().empty()) {
|
||||
if (domain.shellElements().size() >
|
||||
if (!domain.ShellElements().empty()) {
|
||||
if (domain.ShellElements().size() >
|
||||
(std::numeric_limits<std::size_t>::max)() /
|
||||
kShellContributionCount) {
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-dimensions",
|
||||
{domain.sourcePath(), 0U},
|
||||
std::to_string(domain.shellElements().size()),
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(domain.ShellElements().size()),
|
||||
"Shell contribution storage exceeds the addressable range.");
|
||||
}
|
||||
|
||||
std::vector<std::optional<std::array<double, 3>>> directorsByNode(
|
||||
domain.nodes().size());
|
||||
for (const auto& frame : domain.shellNodeInitialFrames()) {
|
||||
if (frame.nodeIndex >= directorsByNode.size() ||
|
||||
directorsByNode[frame.nodeIndex]) {
|
||||
domain.Nodes().size());
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
if (frame.node_index >= directorsByNode.size() ||
|
||||
directorsByNode[frame.node_index]) {
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-element",
|
||||
{domain.sourcePath(), 0U},
|
||||
std::to_string(frame.nodeIndex),
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(frame.node_index),
|
||||
"Shell initial frames must map uniquely to model nodes.");
|
||||
}
|
||||
directorsByNode[frame.nodeIndex] = frame.director;
|
||||
directorsByNode[frame.node_index] = frame.director;
|
||||
}
|
||||
|
||||
struct ShellInput {
|
||||
@@ -102,23 +102,23 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
std::array<std::size_t, kShellElementDofCount> scatter;
|
||||
};
|
||||
std::vector<ShellInput> inputs;
|
||||
inputs.reserve(domain.shellElements().size());
|
||||
inputs.reserve(domain.ShellElements().size());
|
||||
for (std::size_t elementOrder = 0U;
|
||||
elementOrder < domain.shellElements().size();
|
||||
elementOrder < domain.ShellElements().size();
|
||||
++elementOrder) {
|
||||
const auto& element = domain.shellElements()[elementOrder];
|
||||
if (element.materialIndex >= domain.materials().size() ||
|
||||
element.sectionIndex >= domain.shellSections().size()) {
|
||||
const auto& element = domain.ShellElements()[elementOrder];
|
||||
if (element.material_index >= domain.Materials().size() ||
|
||||
element.section_index >= domain.ShellSections().size()) {
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-element",
|
||||
element.location,
|
||||
element.sourceId.source_label_text,
|
||||
element.source_id.source_label_text,
|
||||
"Shell element references an entity outside the Domain.");
|
||||
}
|
||||
|
||||
ShellInput input{};
|
||||
input.section = &domain.shellSections()[element.sectionIndex];
|
||||
input.material = &domain.materials()[element.materialIndex];
|
||||
input.section = &domain.ShellSections()[element.section_index];
|
||||
input.material = &domain.Materials()[element.material_index];
|
||||
try {
|
||||
input.scatter = dofs.shellElementScatter(
|
||||
static_cast<EntityIndex>(elementOrder));
|
||||
@@ -126,22 +126,22 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-scatter",
|
||||
element.location,
|
||||
element.sourceId.source_label_text,
|
||||
element.source_id.source_label_text,
|
||||
"DofManager does not contain the active shell scatter.");
|
||||
}
|
||||
for (std::size_t nodePosition = 0U;
|
||||
nodePosition < element.nodeIndices.size();
|
||||
nodePosition < element.node_indices.size();
|
||||
++nodePosition) {
|
||||
const EntityIndex nodeIndex = element.nodeIndices[nodePosition];
|
||||
if (nodeIndex >= domain.nodes().size() ||
|
||||
const EntityIndex nodeIndex = element.node_indices[nodePosition];
|
||||
if (nodeIndex >= domain.Nodes().size() ||
|
||||
!directorsByNode[nodeIndex]) {
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-element",
|
||||
element.location,
|
||||
element.sourceId.source_label_text,
|
||||
element.source_id.source_label_text,
|
||||
"Shell element requires a valid node and initial director.");
|
||||
}
|
||||
input.nodes[nodePosition] = &domain.nodes()[nodeIndex];
|
||||
input.nodes[nodePosition] = &domain.Nodes()[nodeIndex];
|
||||
input.directors[nodePosition] = *directorsByNode[nodeIndex];
|
||||
for (std::size_t component = 0U;
|
||||
component < kDofsPerNode;
|
||||
@@ -156,7 +156,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-scatter",
|
||||
element.location,
|
||||
element.sourceId.source_label_text,
|
||||
element.source_id.source_label_text,
|
||||
"Shell scatter does not match the active model topology.");
|
||||
}
|
||||
}
|
||||
@@ -170,7 +170,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
inputs.size(),
|
||||
[&](const std::size_t elementOrder) {
|
||||
const auto& input = inputs[elementOrder];
|
||||
const auto shell = Mitc4Shell::create(
|
||||
const auto shell = Mitc4Shell::Create(
|
||||
input.nodes,
|
||||
input.directors,
|
||||
*input.section,
|
||||
@@ -179,7 +179,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
localFailures[elementOrder] = shell.GetStatus();
|
||||
return;
|
||||
}
|
||||
const auto stiffness = shell.Value().stiffness();
|
||||
const auto stiffness = shell.Value().Stiffness();
|
||||
if (!stiffness.HasValue()) {
|
||||
localFailures[elementOrder] = stiffness.GetStatus();
|
||||
return;
|
||||
@@ -197,7 +197,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
buffer[localOrder] = {
|
||||
input.scatter[localRow],
|
||||
input.scatter[localColumn],
|
||||
stiffness.Value().stabilizedGlobal24(
|
||||
stiffness.Value().stabilized_global24(
|
||||
localRow, localColumn),
|
||||
elementOrder,
|
||||
localOrder};
|
||||
@@ -235,7 +235,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
kBeamContributionCount) {
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-dimensions",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(model.activeElements().size()),
|
||||
"Element contribution storage exceeds the addressable range.");
|
||||
}
|
||||
@@ -243,22 +243,22 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
std::vector<std::array<std::size_t, kBeamElementDofCount>> scatters;
|
||||
scatters.reserve(model.activeElements().size());
|
||||
for (const EntityIndex elementIndex : model.activeElements()) {
|
||||
if (elementIndex >= domain.elements().size()) {
|
||||
if (elementIndex >= domain.Elements().size()) {
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-element",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(elementIndex),
|
||||
"Active element index is outside the Domain.");
|
||||
}
|
||||
const auto& element = domain.elements()[elementIndex];
|
||||
if (element.nodeIndices[0U] >= domain.nodes().size() ||
|
||||
element.nodeIndices[1U] >= domain.nodes().size() ||
|
||||
element.materialIndex >= domain.materials().size() ||
|
||||
element.sectionIndex >= domain.sections().size()) {
|
||||
const auto& element = domain.Elements()[elementIndex];
|
||||
if (element.node_indices[0U] >= domain.Nodes().size() ||
|
||||
element.node_indices[1U] >= domain.Nodes().size() ||
|
||||
element.material_index >= domain.Materials().size() ||
|
||||
element.section_index >= domain.Sections().size()) {
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-element",
|
||||
element.location,
|
||||
element.sourceId.source_label_text,
|
||||
element.source_id.source_label_text,
|
||||
"Element references an entity outside the Domain.");
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-scatter",
|
||||
element.location,
|
||||
element.sourceId.source_label_text,
|
||||
element.source_id.source_label_text,
|
||||
"DofManager does not contain the active element scatter.");
|
||||
}
|
||||
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
|
||||
@@ -278,7 +278,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
++component) {
|
||||
const std::size_t local = endpoint * kDofsPerNode + component;
|
||||
const std::size_t expected =
|
||||
static_cast<std::size_t>(element.nodeIndices[endpoint]) *
|
||||
static_cast<std::size_t>(element.node_indices[endpoint]) *
|
||||
kDofsPerNode +
|
||||
component;
|
||||
if (scatter[local] != expected ||
|
||||
@@ -286,7 +286,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
return assemblyFailure(
|
||||
"invalid-assembly-scatter",
|
||||
element.location,
|
||||
element.sourceId.source_label_text,
|
||||
element.source_id.source_label_text,
|
||||
"Element scatter does not match the active model topology.");
|
||||
}
|
||||
}
|
||||
@@ -301,18 +301,18 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
||||
model.activeElements().size(),
|
||||
[&](const std::size_t elementOrder) {
|
||||
const EntityIndex elementIndex = model.activeElements()[elementOrder];
|
||||
const auto& definition = domain.elements()[elementIndex];
|
||||
const auto beam = EulerBeam3D::create(
|
||||
domain.nodes()[definition.nodeIndices[0U]],
|
||||
domain.nodes()[definition.nodeIndices[1U]],
|
||||
domain.sections()[definition.sectionIndex],
|
||||
domain.materials()[definition.materialIndex]);
|
||||
const auto& definition = domain.Elements()[elementIndex];
|
||||
const auto beam = EulerBeam3D::Create(
|
||||
domain.Nodes()[definition.node_indices[0U]],
|
||||
domain.Nodes()[definition.node_indices[1U]],
|
||||
domain.Sections()[definition.section_index],
|
||||
domain.Materials()[definition.material_index]);
|
||||
if (!beam.HasValue()) {
|
||||
localFailures[elementOrder] = beam.GetStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
const Matrix stiffness = beam.Value().globalStiffness();
|
||||
const Matrix stiffness = beam.Value().GlobalStiffness();
|
||||
auto& buffer = localBuffers[elementOrder];
|
||||
const auto& scatter = scatters[elementOrder];
|
||||
for (std::size_t localRow = 0U;
|
||||
|
||||
+390
-425
@@ -1,4 +1,4 @@
|
||||
#include "fesa/elements/euler_beam_3d.hpp"
|
||||
#include "fesa/elements/euler_beam_3d.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -19,497 +19,462 @@ constexpr double kStiffnessInvariantTolerance = 1.0e-12;
|
||||
|
||||
using Vector3 = std::array<double, 3>;
|
||||
|
||||
double norm(const Vector3& value) {
|
||||
return std::hypot(value[0], value[1], value[2]);
|
||||
double Norm(const Vector3& value) {
|
||||
return std::hypot(value[0], value[1], value[2]);
|
||||
}
|
||||
|
||||
double dot(const Vector3& lhs, const Vector3& rhs) {
|
||||
return lhs[0] * rhs[0] + lhs[1] * rhs[1] + lhs[2] * rhs[2];
|
||||
double Dot(const Vector3& lhs, const Vector3& rhs) {
|
||||
return lhs[0] * rhs[0] + lhs[1] * rhs[1] + lhs[2] * rhs[2];
|
||||
}
|
||||
|
||||
Vector3 cross(const Vector3& lhs, const Vector3& rhs) {
|
||||
return {
|
||||
lhs[1] * rhs[2] - lhs[2] * rhs[1],
|
||||
lhs[2] * rhs[0] - lhs[0] * rhs[2],
|
||||
lhs[0] * rhs[1] - lhs[1] * rhs[0]};
|
||||
Vector3 Cross(const Vector3& lhs, const Vector3& rhs) {
|
||||
return {lhs[1] * rhs[2] - lhs[2] * rhs[1], lhs[2] * rhs[0] - lhs[0] * rhs[2],
|
||||
lhs[0] * rhs[1] - lhs[1] * rhs[0]};
|
||||
}
|
||||
|
||||
bool isFinite(const Vector3& value) {
|
||||
return std::isfinite(value[0]) && std::isfinite(value[1]) &&
|
||||
std::isfinite(value[2]);
|
||||
bool IsFinite(const Vector3& value) {
|
||||
return std::isfinite(value[0]) && std::isfinite(value[1]) &&
|
||||
std::isfinite(value[2]);
|
||||
}
|
||||
|
||||
std::string elementIdentity(const Node& firstNode, const Node& secondNode) {
|
||||
return firstNode.sourceId.instance_name + ":" +
|
||||
firstNode.sourceId.source_label_text + "-" +
|
||||
secondNode.sourceId.source_label_text;
|
||||
std::string ElementIdentity(const Node& first_node, const Node& second_node) {
|
||||
return first_node.source_id.instance_name + ":" +
|
||||
first_node.source_id.source_label_text + "-" +
|
||||
second_node.source_id.source_label_text;
|
||||
}
|
||||
|
||||
Result<EulerBeam3D> modelFailure(const std::string& code,
|
||||
Result<EulerBeam3D> ModelFailure(const std::string& code,
|
||||
const SourceLocation& location,
|
||||
const std::string& identity,
|
||||
const std::string& message) {
|
||||
return Result<EulerBeam3D>::Failure(Status::Failure(
|
||||
FailureCategory::kModel,
|
||||
{{Severity::kError, code, location, "*ELEMENT", identity, message}}));
|
||||
return Result<EulerBeam3D>::Failure(Status::Failure(
|
||||
FailureCategory::kModel,
|
||||
{{Severity::kError, code, location, "*ELEMENT", identity, message}}));
|
||||
}
|
||||
|
||||
Matrix transformation(const std::array<double, 9>& rotation) {
|
||||
Matrix result{kElementDofCount, kElementDofCount};
|
||||
// Blocks preserve [translation, rotation] at node 1 then node 2.
|
||||
for (std::size_t block = 0; block < 4U; ++block) {
|
||||
for (std::size_t row = 0; row < 3U; ++row) {
|
||||
for (std::size_t column = 0; column < 3U; ++column) {
|
||||
result(block * 3U + row, block * 3U + column) =
|
||||
rotation[row * 3U + column];
|
||||
}
|
||||
}
|
||||
/// @brief Repeats the local-axis rotation in stable nodal translation and
|
||||
/// rotation blocks.
|
||||
Matrix Transformation(const std::array<double, 9>& rotation) {
|
||||
Matrix result{kElementDofCount, kElementDofCount};
|
||||
// Blocks preserve [translation, rotation] at node 1 then node 2.
|
||||
for (std::size_t block = 0; block < 4U; ++block) {
|
||||
for (std::size_t row = 0; row < 3U; ++row) {
|
||||
for (std::size_t column = 0; column < 3U; ++column) {
|
||||
result(block * 3U + row, block * 3U + column) =
|
||||
rotation[row * 3U + column];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Matrix strainDisplacement(double xi, double length) {
|
||||
Matrix b{kGeneralizedComponentCount, kElementDofCount};
|
||||
const double r = 0.5 * (1.0 + xi);
|
||||
const double inverseLength = 1.0 / length;
|
||||
const double inverseLengthSquared = inverseLength * inverseLength;
|
||||
/// @brief Builds the reviewed axial, twist, and signed curvature operator.
|
||||
/// @warning The theta-y and theta-z signs are formulation contracts.
|
||||
Matrix StrainDisplacement(double xi, double length) {
|
||||
Matrix b{kGeneralizedComponentCount, kElementDofCount};
|
||||
const double r = 0.5 * (1.0 + xi);
|
||||
const double inverse_length = 1.0 / length;
|
||||
const double inverse_length_squared = inverse_length * inverse_length;
|
||||
|
||||
b(0U, 0U) = -inverseLength;
|
||||
b(0U, 6U) = inverseLength;
|
||||
b(1U, 3U) = -inverseLength;
|
||||
b(1U, 9U) = inverseLength;
|
||||
b(0U, 0U) = -inverse_length;
|
||||
b(0U, 6U) = inverse_length;
|
||||
b(1U, 3U) = -inverse_length;
|
||||
b(1U, 9U) = inverse_length;
|
||||
|
||||
// theta_y=-w' makes kappa_y=-w''; theta_z=v' makes kappa_z=v''.
|
||||
b(2U, 2U) = (6.0 - 12.0 * r) * inverseLengthSquared;
|
||||
b(2U, 4U) = (-4.0 + 6.0 * r) * inverseLength;
|
||||
b(2U, 8U) = (-6.0 + 12.0 * r) * inverseLengthSquared;
|
||||
b(2U, 10U) = (-2.0 + 6.0 * r) * inverseLength;
|
||||
// theta_y=-w' makes kappa_y=-w''; theta_z=v' makes kappa_z=v''.
|
||||
b(2U, 2U) = (6.0 - 12.0 * r) * inverse_length_squared;
|
||||
b(2U, 4U) = (-4.0 + 6.0 * r) * inverse_length;
|
||||
b(2U, 8U) = (-6.0 + 12.0 * r) * inverse_length_squared;
|
||||
b(2U, 10U) = (-2.0 + 6.0 * r) * inverse_length;
|
||||
|
||||
b(3U, 1U) = (-6.0 + 12.0 * r) * inverseLengthSquared;
|
||||
b(3U, 5U) = (-4.0 + 6.0 * r) * inverseLength;
|
||||
b(3U, 7U) = (6.0 - 12.0 * r) * inverseLengthSquared;
|
||||
b(3U, 11U) = (-2.0 + 6.0 * r) * inverseLength;
|
||||
return b;
|
||||
b(3U, 1U) = (-6.0 + 12.0 * r) * inverse_length_squared;
|
||||
b(3U, 5U) = (-4.0 + 6.0 * r) * inverse_length;
|
||||
b(3U, 7U) = (6.0 - 12.0 * r) * inverse_length_squared;
|
||||
b(3U, 11U) = (-2.0 + 6.0 * r) * inverse_length;
|
||||
return b;
|
||||
}
|
||||
|
||||
std::array<double, kGeneralizedComponentCount> constitutiveDiagonal(
|
||||
double youngsModulus,
|
||||
double shearModulus,
|
||||
double area,
|
||||
double iy,
|
||||
double iz,
|
||||
double torsionalConstant) {
|
||||
return {
|
||||
youngsModulus * area,
|
||||
shearModulus * torsionalConstant,
|
||||
youngsModulus * iy,
|
||||
youngsModulus * iz};
|
||||
std::array<double, kGeneralizedComponentCount> ConstitutiveDiagonal(
|
||||
double youngs_modulus, double shear_modulus, double area, double iy,
|
||||
double iz, double torsional_constant) {
|
||||
return {youngs_modulus * area, shear_modulus * torsional_constant,
|
||||
youngs_modulus * iy, youngs_modulus * iz};
|
||||
}
|
||||
|
||||
Matrix closedStiffness(double length,
|
||||
const std::array<double, kGeneralizedComponentCount>& diagonal) {
|
||||
Matrix closed{kElementDofCount, kElementDofCount};
|
||||
const auto addBlock = [&closed](const std::array<std::size_t, 2>& indices,
|
||||
double coefficient) {
|
||||
closed(indices[0], indices[0]) = coefficient;
|
||||
closed(indices[0], indices[1]) = -coefficient;
|
||||
closed(indices[1], indices[0]) = -coefficient;
|
||||
closed(indices[1], indices[1]) = coefficient;
|
||||
};
|
||||
addBlock({0U, 6U}, diagonal[0U] / length);
|
||||
addBlock({3U, 9U}, diagonal[1U] / length);
|
||||
/// @brief Builds the independent closed-form stiffness used as a Gauss-rule
|
||||
/// invariant.
|
||||
Matrix ClosedStiffness(
|
||||
double length,
|
||||
const std::array<double, kGeneralizedComponentCount>& diagonal) {
|
||||
Matrix closed{kElementDofCount, kElementDofCount};
|
||||
const auto add_block = [&closed](const std::array<std::size_t, 2>& indices,
|
||||
double coefficient) {
|
||||
closed(indices[0], indices[0]) = coefficient;
|
||||
closed(indices[0], indices[1]) = -coefficient;
|
||||
closed(indices[1], indices[0]) = -coefficient;
|
||||
closed(indices[1], indices[1]) = coefficient;
|
||||
};
|
||||
add_block({0U, 6U}, diagonal[0U] / length);
|
||||
add_block({3U, 9U}, diagonal[1U] / length);
|
||||
|
||||
const auto addBendingBlock = [&closed, length](
|
||||
const auto add_bending_block = [&closed, length](
|
||||
const std::array<std::size_t, 4>& indices,
|
||||
double flexuralRigidity,
|
||||
double rotationSign) {
|
||||
const double value = 12.0 * flexuralRigidity /
|
||||
(length * length * length);
|
||||
const double coupling = rotationSign * 6.0 * flexuralRigidity /
|
||||
(length * length);
|
||||
const double diagonalRotation = 4.0 * flexuralRigidity / length;
|
||||
const double offDiagonalRotation = 2.0 * flexuralRigidity / length;
|
||||
const std::array<double, 16> block = {
|
||||
value, coupling, -value, coupling,
|
||||
coupling, diagonalRotation, -coupling, offDiagonalRotation,
|
||||
-value, -coupling, value, -coupling,
|
||||
coupling, offDiagonalRotation, -coupling, diagonalRotation};
|
||||
for (std::size_t row = 0; row < indices.size(); ++row) {
|
||||
for (std::size_t column = 0; column < indices.size(); ++column) {
|
||||
closed(indices[row], indices[column]) = block[row * indices.size() + column];
|
||||
}
|
||||
}
|
||||
};
|
||||
addBendingBlock({1U, 5U, 7U, 11U}, diagonal[3U], 1.0);
|
||||
addBendingBlock({2U, 4U, 8U, 10U}, diagonal[2U], -1.0);
|
||||
return closed;
|
||||
double flexural_rigidity,
|
||||
double rotation_sign) {
|
||||
const double value = 12.0 * flexural_rigidity / (length * length * length);
|
||||
const double coupling =
|
||||
rotation_sign * 6.0 * flexural_rigidity / (length * length);
|
||||
const double diagonal_rotation = 4.0 * flexural_rigidity / length;
|
||||
const double off_diagonal_rotation = 2.0 * flexural_rigidity / length;
|
||||
const std::array<double, 16> block = {value, coupling,
|
||||
-value, coupling,
|
||||
coupling, diagonal_rotation,
|
||||
-coupling, off_diagonal_rotation,
|
||||
-value, -coupling,
|
||||
value, -coupling,
|
||||
coupling, off_diagonal_rotation,
|
||||
-coupling, diagonal_rotation};
|
||||
for (std::size_t row = 0; row < indices.size(); ++row) {
|
||||
for (std::size_t column = 0; column < indices.size(); ++column) {
|
||||
closed(indices[row], indices[column]) =
|
||||
block[row * indices.size() + column];
|
||||
}
|
||||
}
|
||||
};
|
||||
add_bending_block({1U, 5U, 7U, 11U}, diagonal[3U], 1.0);
|
||||
add_bending_block({2U, 4U, 8U, 10U}, diagonal[2U], -1.0);
|
||||
return closed;
|
||||
}
|
||||
|
||||
double normalizedMatrixError(const Matrix& lhs, const Matrix& rhs) {
|
||||
double maximumDifference = 0.0;
|
||||
double scale = 1.0;
|
||||
for (std::size_t row = 0; row < lhs.Rows(); ++row) {
|
||||
for (std::size_t column = 0; column < lhs.Columns(); ++column) {
|
||||
const double lhsValue = lhs(row, column);
|
||||
const double rhsValue = rhs(row, column);
|
||||
if (!std::isfinite(lhsValue) || !std::isfinite(rhsValue)) {
|
||||
return std::numeric_limits<double>::infinity();
|
||||
}
|
||||
const double difference = std::abs(lhsValue - rhsValue);
|
||||
if (!std::isfinite(difference)) {
|
||||
return std::numeric_limits<double>::infinity();
|
||||
}
|
||||
maximumDifference = (std::max)(
|
||||
maximumDifference,
|
||||
difference);
|
||||
scale = (std::max)(scale, std::abs(lhsValue));
|
||||
scale = (std::max)(scale, std::abs(rhsValue));
|
||||
}
|
||||
}
|
||||
if (!std::isfinite(maximumDifference) || !std::isfinite(scale) ||
|
||||
!(scale > 0.0)) {
|
||||
double NormalizedMatrixError(const Matrix& lhs, const Matrix& rhs) {
|
||||
double maximum_difference = 0.0;
|
||||
double scale_value = 1.0;
|
||||
for (std::size_t row = 0; row < lhs.Rows(); ++row) {
|
||||
for (std::size_t column = 0; column < lhs.Columns(); ++column) {
|
||||
const double lhs_value = lhs(row, column);
|
||||
const double rhs_value = rhs(row, column);
|
||||
if (!std::isfinite(lhs_value) || !std::isfinite(rhs_value)) {
|
||||
return std::numeric_limits<double>::infinity();
|
||||
}
|
||||
const double difference = std::abs(lhs_value - rhs_value);
|
||||
if (!std::isfinite(difference)) {
|
||||
return std::numeric_limits<double>::infinity();
|
||||
}
|
||||
maximum_difference = (std::max)(maximum_difference, difference);
|
||||
scale_value = (std::max)(scale_value, std::abs(lhs_value));
|
||||
scale_value = (std::max)(scale_value, std::abs(rhs_value));
|
||||
}
|
||||
const double normalizedError = maximumDifference / scale;
|
||||
return std::isfinite(normalizedError)
|
||||
? normalizedError
|
||||
: std::numeric_limits<double>::infinity();
|
||||
}
|
||||
if (!std::isfinite(maximum_difference) || !std::isfinite(scale_value) ||
|
||||
!(scale_value > 0.0)) {
|
||||
return std::numeric_limits<double>::infinity();
|
||||
}
|
||||
const double normalized_error = maximum_difference / scale_value;
|
||||
return std::isfinite(normalized_error)
|
||||
? normalized_error
|
||||
: std::numeric_limits<double>::infinity();
|
||||
}
|
||||
|
||||
std::array<double, kGeneralizedComponentCount> generalizedStrain(
|
||||
const Matrix& b,
|
||||
const Vector& localDisplacement) {
|
||||
std::array<double, kGeneralizedComponentCount> strain{};
|
||||
for (std::size_t component = 0; component < strain.size(); ++component) {
|
||||
for (std::size_t dof = 0; dof < localDisplacement.Size(); ++dof) {
|
||||
strain[component] += b(component, dof) * localDisplacement[dof];
|
||||
}
|
||||
/// @brief Recovers generalized strain in fixed component and DOF order.
|
||||
std::array<double, kGeneralizedComponentCount> GeneralizedStrain(
|
||||
const Matrix& b, const Vector& local_displacement) {
|
||||
std::array<double, kGeneralizedComponentCount> strain{};
|
||||
for (std::size_t component = 0; component < strain.size(); ++component) {
|
||||
for (std::size_t dof = 0; dof < local_displacement.Size(); ++dof) {
|
||||
strain[component] += b(component, dof) * local_displacement[dof];
|
||||
}
|
||||
return strain;
|
||||
}
|
||||
return strain;
|
||||
}
|
||||
|
||||
std::array<double, kGeneralizedComponentCount> generalizedResultant(
|
||||
/// @brief Applies the diagonal section law without changing component order.
|
||||
std::array<double, kGeneralizedComponentCount> GeneralizedResultant(
|
||||
const std::array<double, kGeneralizedComponentCount>& strain,
|
||||
const std::array<double, kGeneralizedComponentCount>& diagonal) {
|
||||
std::array<double, kGeneralizedComponentCount> resultant{};
|
||||
for (std::size_t component = 0; component < resultant.size(); ++component) {
|
||||
resultant[component] = diagonal[component] * strain[component];
|
||||
}
|
||||
return resultant;
|
||||
std::array<double, kGeneralizedComponentCount> resultant{};
|
||||
for (std::size_t component = 0; component < resultant.size(); ++component) {
|
||||
resultant[component] = diagonal[component] * strain[component];
|
||||
}
|
||||
return resultant;
|
||||
}
|
||||
|
||||
Matrix kinematicInterpolation(double xi, double length) {
|
||||
Matrix interpolation{4U, kElementDofCount};
|
||||
const double r = 0.5 * (1.0 + xi);
|
||||
const double rSquared = r * r;
|
||||
const double rCubed = rSquared * r;
|
||||
const double n1 = 1.0 - r;
|
||||
const double n2 = r;
|
||||
const double h1 = 1.0 - 3.0 * rSquared + 2.0 * rCubed;
|
||||
const double h2 = length * (r - 2.0 * rSquared + rCubed);
|
||||
const double h3 = 3.0 * rSquared - 2.0 * rCubed;
|
||||
const double h4 = length * (-rSquared + rCubed);
|
||||
/// @brief Builds signed Hermite interpolation for endpoint recovery.
|
||||
Matrix KinematicInterpolation(double xi, double length) {
|
||||
Matrix interpolation{4U, kElementDofCount};
|
||||
const double r = 0.5 * (1.0 + xi);
|
||||
const double r_squared = r * r;
|
||||
const double r_cubed = r_squared * r;
|
||||
const double n1 = 1.0 - r;
|
||||
const double n2 = r;
|
||||
const double h1 = 1.0 - 3.0 * r_squared + 2.0 * r_cubed;
|
||||
const double h2 = length * (r - 2.0 * r_squared + r_cubed);
|
||||
const double h3 = 3.0 * r_squared - 2.0 * r_cubed;
|
||||
const double h4 = length * (-r_squared + r_cubed);
|
||||
|
||||
interpolation(0U, 0U) = n1;
|
||||
interpolation(0U, 6U) = n2;
|
||||
interpolation(1U, 1U) = h1;
|
||||
interpolation(1U, 5U) = h2;
|
||||
interpolation(1U, 7U) = h3;
|
||||
interpolation(1U, 11U) = h4;
|
||||
interpolation(2U, 2U) = h1;
|
||||
interpolation(2U, 4U) = -h2;
|
||||
interpolation(2U, 8U) = h3;
|
||||
interpolation(2U, 10U) = -h4;
|
||||
interpolation(3U, 3U) = n1;
|
||||
interpolation(3U, 9U) = n2;
|
||||
return interpolation;
|
||||
interpolation(0U, 0U) = n1;
|
||||
interpolation(0U, 6U) = n2;
|
||||
interpolation(1U, 1U) = h1;
|
||||
interpolation(1U, 5U) = h2;
|
||||
interpolation(1U, 7U) = h3;
|
||||
interpolation(1U, 11U) = h4;
|
||||
interpolation(2U, 2U) = h1;
|
||||
interpolation(2U, 4U) = -h2;
|
||||
interpolation(2U, 8U) = h3;
|
||||
interpolation(2U, 10U) = -h4;
|
||||
interpolation(3U, 3U) = n1;
|
||||
interpolation(3U, 9U) = n2;
|
||||
return interpolation;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
Result<EulerBeam3D> EulerBeam3D::create(
|
||||
const Node& firstNode,
|
||||
const Node& secondNode,
|
||||
const GeneralBeamSection& section,
|
||||
const LinearElasticMaterial& material) {
|
||||
const std::string identity = elementIdentity(firstNode, secondNode);
|
||||
const Vector3& first = firstNode.coordinates;
|
||||
const Vector3& second = secondNode.coordinates;
|
||||
const Vector3 delta = {
|
||||
second[0] - first[0], second[1] - first[1], second[2] - first[2]};
|
||||
const double length = norm(delta);
|
||||
const double coordinateScale =
|
||||
(std::max)({1.0, norm(first), norm(second)});
|
||||
if (!isFinite(first) || !isFinite(second) || !isFinite(delta) ||
|
||||
!std::isfinite(length) || !std::isfinite(coordinateScale) ||
|
||||
!(length > kGeometryTolerance * coordinateScale)) {
|
||||
return modelFailure(
|
||||
"invalid-beam-length",
|
||||
firstNode.location,
|
||||
identity,
|
||||
"Beam length must exceed the scale-aware geometry threshold.");
|
||||
}
|
||||
Result<EulerBeam3D> EulerBeam3D::Create(const Node& first_node,
|
||||
const Node& second_node,
|
||||
const GeneralBeamSection& section,
|
||||
const LinearElasticMaterial& material) {
|
||||
const std::string identity = ElementIdentity(first_node, second_node);
|
||||
const Vector3& first = first_node.coordinates;
|
||||
const Vector3& second = second_node.coordinates;
|
||||
const Vector3 delta = {second[0] - first[0], second[1] - first[1],
|
||||
second[2] - first[2]};
|
||||
const double length = Norm(delta);
|
||||
const double coordinate_scale = (std::max)({1.0, Norm(first), Norm(second)});
|
||||
if (!IsFinite(first) || !IsFinite(second) || !IsFinite(delta) ||
|
||||
!std::isfinite(length) || !std::isfinite(coordinate_scale) ||
|
||||
!(length > kGeometryTolerance * coordinate_scale)) {
|
||||
return ModelFailure(
|
||||
"invalid-beam-length", first_node.location, identity,
|
||||
"Beam length must exceed the scale-aware geometry threshold.");
|
||||
}
|
||||
|
||||
const Vector3 ex = {delta[0] / length, delta[1] / length, delta[2] / length};
|
||||
const Vector3& guide = section.firstAxis;
|
||||
const double guideNorm = norm(guide);
|
||||
const double guideProjection = dot(guide, ex);
|
||||
const Vector3 eyTrial = {
|
||||
guide[0] - guideProjection * ex[0],
|
||||
guide[1] - guideProjection * ex[1],
|
||||
guide[2] - guideProjection * ex[2]};
|
||||
const double eyTrialNorm = norm(eyTrial);
|
||||
if (!isFinite(guide) || !std::isfinite(guideNorm) || !isFinite(eyTrial) ||
|
||||
!std::isfinite(eyTrialNorm) ||
|
||||
!(eyTrialNorm > kGeometryTolerance * (std::max)(1.0, guideNorm))) {
|
||||
return modelFailure(
|
||||
"invalid-beam-guide-vector",
|
||||
section.location,
|
||||
identity,
|
||||
"Beam guide vector must define a scale-aware transverse direction.");
|
||||
}
|
||||
const Vector3 ex = {delta[0] / length, delta[1] / length, delta[2] / length};
|
||||
const Vector3& guide = section.first_axis;
|
||||
const double guide_norm = Norm(guide);
|
||||
const double guide_projection = Dot(guide, ex);
|
||||
const Vector3 ey_trial = {guide[0] - guide_projection * ex[0],
|
||||
guide[1] - guide_projection * ex[1],
|
||||
guide[2] - guide_projection * ex[2]};
|
||||
const double ey_trial_norm = Norm(ey_trial);
|
||||
if (!IsFinite(guide) || !std::isfinite(guide_norm) || !IsFinite(ey_trial) ||
|
||||
!std::isfinite(ey_trial_norm) ||
|
||||
!(ey_trial_norm > kGeometryTolerance * (std::max)(1.0, guide_norm))) {
|
||||
return ModelFailure(
|
||||
"invalid-beam-guide-vector", section.location, identity,
|
||||
"Beam guide vector must define a scale-aware transverse direction.");
|
||||
}
|
||||
|
||||
if (!std::isfinite(section.i12)) {
|
||||
return modelFailure(
|
||||
"invalid-beam-property",
|
||||
section.location,
|
||||
identity,
|
||||
"Beam section properties must be finite and positive.");
|
||||
}
|
||||
if (section.i12 != 0.0) {
|
||||
return modelFailure(
|
||||
"unsupported-coupled-section",
|
||||
section.location,
|
||||
identity,
|
||||
"The Euler beam kernel requires exact I12=0.");
|
||||
}
|
||||
if (!std::isfinite(section.i12)) {
|
||||
return ModelFailure("invalid-beam-property", section.location, identity,
|
||||
"Beam section properties must be finite and positive.");
|
||||
}
|
||||
if (section.i12 != 0.0) {
|
||||
return ModelFailure("unsupported-coupled-section", section.location,
|
||||
identity,
|
||||
"The Euler beam kernel requires exact I12=0.");
|
||||
}
|
||||
|
||||
const double shearModulus =
|
||||
material.youngsModulus / (2.0 * (1.0 + material.poissonRatio));
|
||||
const std::array<double, 6> positiveProperties = {
|
||||
material.youngsModulus,
|
||||
shearModulus,
|
||||
section.area,
|
||||
section.i11,
|
||||
section.i22,
|
||||
section.torsionalConstant};
|
||||
if (!std::isfinite(material.poissonRatio) ||
|
||||
std::any_of(
|
||||
positiveProperties.begin(),
|
||||
positiveProperties.end(),
|
||||
[](double property) { return !std::isfinite(property) || !(property > 0.0); })) {
|
||||
return modelFailure(
|
||||
"invalid-beam-property",
|
||||
section.location,
|
||||
identity,
|
||||
"E, G, A, Iy, Iz, and J must be finite and positive.");
|
||||
}
|
||||
const double shear_modulus =
|
||||
material.youngs_modulus / (2.0 * (1.0 + material.poisson_ratio));
|
||||
const std::array<double, 6> positive_properties = {
|
||||
material.youngs_modulus,
|
||||
shear_modulus,
|
||||
section.area,
|
||||
section.i11,
|
||||
section.i22,
|
||||
section.torsional_constant};
|
||||
if (!std::isfinite(material.poisson_ratio) ||
|
||||
std::any_of(positive_properties.begin(), positive_properties.end(),
|
||||
[](double property) {
|
||||
return !std::isfinite(property) || !(property > 0.0);
|
||||
})) {
|
||||
return ModelFailure("invalid-beam-property", section.location, identity,
|
||||
"E, G, A, Iy, Iz, and J must be finite and positive.");
|
||||
}
|
||||
|
||||
const auto derivedRigidity = constitutiveDiagonal(
|
||||
material.youngsModulus,
|
||||
shearModulus,
|
||||
section.area,
|
||||
section.i11,
|
||||
section.i22,
|
||||
section.torsionalConstant);
|
||||
const double lengthSquared = length * length;
|
||||
const double lengthCubed = lengthSquared * length;
|
||||
// These are every distinct positive magnitude used by the exact axial,
|
||||
// torsion, and two bending closed-form blocks. Reject arithmetic
|
||||
// overflow/underflow without introducing a conditioning threshold.
|
||||
const std::array<double, 14> requiredStiffnessMagnitudes = {
|
||||
derivedRigidity[0U],
|
||||
derivedRigidity[1U],
|
||||
derivedRigidity[2U],
|
||||
derivedRigidity[3U],
|
||||
derivedRigidity[0U] / length,
|
||||
derivedRigidity[1U] / length,
|
||||
12.0 * derivedRigidity[2U] / lengthCubed,
|
||||
6.0 * derivedRigidity[2U] / lengthSquared,
|
||||
4.0 * derivedRigidity[2U] / length,
|
||||
2.0 * derivedRigidity[2U] / length,
|
||||
12.0 * derivedRigidity[3U] / lengthCubed,
|
||||
6.0 * derivedRigidity[3U] / lengthSquared,
|
||||
4.0 * derivedRigidity[3U] / length,
|
||||
2.0 * derivedRigidity[3U] / length};
|
||||
if (std::any_of(
|
||||
requiredStiffnessMagnitudes.begin(),
|
||||
requiredStiffnessMagnitudes.end(),
|
||||
[](double magnitude) {
|
||||
return !std::isfinite(magnitude) || !(magnitude > 0.0);
|
||||
})) {
|
||||
return modelFailure(
|
||||
"invalid-beam-property",
|
||||
section.location,
|
||||
identity,
|
||||
"Derived beam stiffness coefficients must be finite and positive.");
|
||||
}
|
||||
const auto derived_rigidity = ConstitutiveDiagonal(
|
||||
material.youngs_modulus, shear_modulus, section.area, section.i11,
|
||||
section.i22, section.torsional_constant);
|
||||
const double length_squared = length * length;
|
||||
const double length_cubed = length_squared * length;
|
||||
// These are every distinct positive magnitude used by the exact axial,
|
||||
// torsion, and two bending closed-form blocks. Reject arithmetic
|
||||
// overflow/underflow without introducing a conditioning threshold.
|
||||
const std::array<double, 14> required_stiffness_magnitudes = {
|
||||
derived_rigidity[0U],
|
||||
derived_rigidity[1U],
|
||||
derived_rigidity[2U],
|
||||
derived_rigidity[3U],
|
||||
derived_rigidity[0U] / length,
|
||||
derived_rigidity[1U] / length,
|
||||
12.0 * derived_rigidity[2U] / length_cubed,
|
||||
6.0 * derived_rigidity[2U] / length_squared,
|
||||
4.0 * derived_rigidity[2U] / length,
|
||||
2.0 * derived_rigidity[2U] / length,
|
||||
12.0 * derived_rigidity[3U] / length_cubed,
|
||||
6.0 * derived_rigidity[3U] / length_squared,
|
||||
4.0 * derived_rigidity[3U] / length,
|
||||
2.0 * derived_rigidity[3U] / length};
|
||||
if (std::any_of(required_stiffness_magnitudes.begin(),
|
||||
required_stiffness_magnitudes.end(), [](double magnitude) {
|
||||
return !std::isfinite(magnitude) || !(magnitude > 0.0);
|
||||
})) {
|
||||
return ModelFailure(
|
||||
"invalid-beam-property", section.location, identity,
|
||||
"Derived beam stiffness coefficients must be finite and positive.");
|
||||
}
|
||||
|
||||
const Vector3 ey = {
|
||||
eyTrial[0] / eyTrialNorm,
|
||||
eyTrial[1] / eyTrialNorm,
|
||||
eyTrial[2] / eyTrialNorm};
|
||||
const Vector3 ez = cross(ex, ey);
|
||||
// Rows map global vectors to the approved right-handed local (ex,ey,ez) basis.
|
||||
const std::array<double, 9> rotation = {
|
||||
ex[0], ex[1], ex[2],
|
||||
ey[0], ey[1], ey[2],
|
||||
ez[0], ez[1], ez[2]};
|
||||
const Vector3 ey = {ey_trial[0] / ey_trial_norm, ey_trial[1] / ey_trial_norm,
|
||||
ey_trial[2] / ey_trial_norm};
|
||||
const Vector3 ez = Cross(ex, ey);
|
||||
// Rows map global vectors to the approved right-handed local (ex,ey,ez)
|
||||
// basis.
|
||||
const std::array<double, 9> rotation = {ex[0], ex[1], ex[2], ey[0], ey[1],
|
||||
ey[2], ez[0], ez[1], ez[2]};
|
||||
|
||||
return Result<EulerBeam3D>::Success(EulerBeam3D{
|
||||
length,
|
||||
material.youngsModulus,
|
||||
shearModulus,
|
||||
section.area,
|
||||
section.i11,
|
||||
section.i22,
|
||||
section.torsionalConstant,
|
||||
rotation,
|
||||
section.sectionPoints});
|
||||
return Result<EulerBeam3D>::Success(
|
||||
EulerBeam3D{length, material.youngs_modulus, shear_modulus, section.area,
|
||||
section.i11, section.i22, section.torsional_constant,
|
||||
rotation, section.section_points});
|
||||
}
|
||||
|
||||
Matrix EulerBeam3D::localStiffness() const {
|
||||
const auto diagonal = constitutiveDiagonal(
|
||||
youngsModulus_, shearModulus_, area_, iy_, iz_, torsionalConstant_);
|
||||
Matrix stiffness{kElementDofCount, kElementDofCount};
|
||||
const double inverseSqrtThree = 1.0 / std::sqrt(3.0);
|
||||
const std::array<double, 2> gaussPoints = {-inverseSqrtThree, inverseSqrtThree};
|
||||
const double jacobian = 0.5 * length_;
|
||||
Matrix EulerBeam3D::LocalStiffness() const {
|
||||
const auto diagonal = ConstitutiveDiagonal(
|
||||
youngs_modulus_, shear_modulus_, area_, iy_, iz_, torsional_constant_);
|
||||
Matrix stiffness{kElementDofCount, kElementDofCount};
|
||||
const double inverse_sqrt_three = 1.0 / std::sqrt(3.0);
|
||||
const std::array<double, 2> gauss_points = {-inverse_sqrt_three,
|
||||
inverse_sqrt_three};
|
||||
const double jacobian = 0.5 * length_;
|
||||
|
||||
// Both Gauss points are required: a one-point bending rule loses two ranks.
|
||||
for (const double xi : gaussPoints) {
|
||||
const Matrix b = strainDisplacement(xi, length_);
|
||||
for (std::size_t row = 0; row < kElementDofCount; ++row) {
|
||||
for (std::size_t column = 0; column < kElementDofCount; ++column) {
|
||||
for (std::size_t component = 0; component < diagonal.size(); ++component) {
|
||||
stiffness(row, column) +=
|
||||
b(component, row) * diagonal[component] *
|
||||
b(component, column) * jacobian;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Matrix closed = closedStiffness(length_, diagonal);
|
||||
if (normalizedMatrixError(stiffness, closed) > kStiffnessInvariantTolerance) {
|
||||
throw std::logic_error{
|
||||
"Two-point Euler beam stiffness violated the closed-form invariant."};
|
||||
}
|
||||
return stiffness;
|
||||
}
|
||||
|
||||
Matrix EulerBeam3D::globalStiffness() const {
|
||||
const Matrix local = localStiffness();
|
||||
const Matrix transform = transformation(rotation_);
|
||||
const Matrix localTimesTransform = local.Multiply(transform);
|
||||
Matrix global{kElementDofCount, kElementDofCount};
|
||||
// Kg=T^T*Kl*T while dl=T*dg.
|
||||
// Both Gauss points are required: a one-point bending rule loses two ranks.
|
||||
for (const double xi : gauss_points) {
|
||||
const Matrix b = StrainDisplacement(xi, length_);
|
||||
for (std::size_t row = 0; row < kElementDofCount; ++row) {
|
||||
for (std::size_t column = 0; column < kElementDofCount; ++column) {
|
||||
for (std::size_t inner = 0; inner < kElementDofCount; ++inner) {
|
||||
global(row, column) +=
|
||||
transform(inner, row) * localTimesTransform(inner, column);
|
||||
}
|
||||
for (std::size_t column = 0; column < kElementDofCount; ++column) {
|
||||
for (std::size_t component = 0; component < diagonal.size();
|
||||
++component) {
|
||||
stiffness(row, column) += b(component, row) * diagonal[component] *
|
||||
b(component, column) * jacobian;
|
||||
}
|
||||
}
|
||||
}
|
||||
return global;
|
||||
}
|
||||
|
||||
const Matrix closed = ClosedStiffness(length_, diagonal);
|
||||
if (NormalizedMatrixError(stiffness, closed) > kStiffnessInvariantTolerance) {
|
||||
throw std::logic_error{
|
||||
"Two-point Euler beam stiffness violated the closed-form invariant."};
|
||||
}
|
||||
return stiffness;
|
||||
}
|
||||
|
||||
Vector EulerBeam3D::localEquivalentLoad(const ConstantLocalLineLoad& load) const {
|
||||
const std::array<double, 4> components = {load.px, load.py, load.pz, load.mx};
|
||||
Vector equivalent{kElementDofCount};
|
||||
const double inverseSqrtThree = 1.0 / std::sqrt(3.0);
|
||||
const std::array<double, 2> gaussPoints = {-inverseSqrtThree, inverseSqrtThree};
|
||||
const double jacobian = 0.5 * length_;
|
||||
for (const double xi : gaussPoints) {
|
||||
const Matrix interpolation = kinematicInterpolation(xi, length_);
|
||||
for (std::size_t dof = 0; dof < equivalent.Size(); ++dof) {
|
||||
for (std::size_t component = 0; component < components.size(); ++component) {
|
||||
equivalent[dof] +=
|
||||
interpolation(component, dof) * components[component] * jacobian;
|
||||
}
|
||||
}
|
||||
Matrix EulerBeam3D::GlobalStiffness() const {
|
||||
const Matrix local = LocalStiffness();
|
||||
const Matrix transform = Transformation(rotation_);
|
||||
const Matrix local_times_transform = local.Multiply(transform);
|
||||
Matrix global{kElementDofCount, kElementDofCount};
|
||||
// Kg=T^T*Kl*T while dl=T*dg.
|
||||
for (std::size_t row = 0; row < kElementDofCount; ++row) {
|
||||
for (std::size_t column = 0; column < kElementDofCount; ++column) {
|
||||
for (std::size_t inner = 0; inner < kElementDofCount; ++inner) {
|
||||
global(row, column) +=
|
||||
transform(inner, row) * local_times_transform(inner, column);
|
||||
}
|
||||
}
|
||||
return equivalent;
|
||||
}
|
||||
return global;
|
||||
}
|
||||
|
||||
BeamRecovery EulerBeam3D::recover(const Vector& globalElementDisplacement) const {
|
||||
const Matrix transform = transformation(rotation_);
|
||||
const Vector localDisplacement = transform.Multiply(globalElementDisplacement);
|
||||
const auto diagonal = constitutiveDiagonal(
|
||||
youngsModulus_, shearModulus_, area_, iy_, iz_, torsionalConstant_);
|
||||
BeamRecovery recovery{};
|
||||
|
||||
// With parser/CLI distributed loading excluded, Kl*dl is the local outward end action.
|
||||
const Vector endAction = localStiffness().Multiply(localDisplacement);
|
||||
for (std::size_t endpoint = 0; endpoint < 2U; ++endpoint) {
|
||||
for (std::size_t component = 0; component < 6U; ++component) {
|
||||
recovery.equilibriumEndActions[endpoint][component] =
|
||||
endAction[endpoint * 6U + component];
|
||||
}
|
||||
|
||||
const double xi = endpoint == 0U ? -1.0 : 1.0;
|
||||
recovery.endpointSectionResultants[endpoint] = generalizedResultant(
|
||||
generalizedStrain(strainDisplacement(xi, length_), localDisplacement),
|
||||
diagonal);
|
||||
Vector EulerBeam3D::LocalEquivalentLoad(
|
||||
const ConstantLocalLineLoad& load) const {
|
||||
const std::array<double, 4> components = {load.px, load.py, load.pz, load.mx};
|
||||
Vector equivalent{kElementDofCount};
|
||||
const double inverse_sqrt_three = 1.0 / std::sqrt(3.0);
|
||||
const std::array<double, 2> gauss_points = {-inverse_sqrt_three,
|
||||
inverse_sqrt_three};
|
||||
const double jacobian = 0.5 * length_;
|
||||
for (const double xi : gauss_points) {
|
||||
const Matrix interpolation = KinematicInterpolation(xi, length_);
|
||||
for (std::size_t dof = 0; dof < equivalent.Size(); ++dof) {
|
||||
for (std::size_t component = 0; component < components.size();
|
||||
++component) {
|
||||
equivalent[dof] +=
|
||||
interpolation(component, dof) * components[component] * jacobian;
|
||||
}
|
||||
}
|
||||
|
||||
const double inverseSqrtThree = 1.0 / std::sqrt(3.0);
|
||||
const std::array<double, 2> gaussPoints = {-inverseSqrtThree, inverseSqrtThree};
|
||||
for (std::size_t point = 0; point < gaussPoints.size(); ++point) {
|
||||
recovery.gaussGeneralizedStrains[point] = generalizedStrain(
|
||||
strainDisplacement(gaussPoints[point], length_), localDisplacement);
|
||||
recovery.gaussGeneralizedResultants[point] = generalizedResultant(
|
||||
recovery.gaussGeneralizedStrains[point], diagonal);
|
||||
|
||||
if (sectionPoints_.empty()) {
|
||||
recovery.stressPoints.push_back({
|
||||
static_cast<int>(point + 1U),
|
||||
0U,
|
||||
0.0,
|
||||
0.0,
|
||||
youngsModulus_ * recovery.gaussGeneralizedStrains[point][0U],
|
||||
"fesa-default"});
|
||||
continue;
|
||||
}
|
||||
|
||||
for (std::size_t sectionPoint = 0; sectionPoint < sectionPoints_.size();
|
||||
++sectionPoint) {
|
||||
const double x1 = sectionPoints_[sectionPoint][0U];
|
||||
const double x2 = sectionPoints_[sectionPoint][1U];
|
||||
const auto& strain = recovery.gaussGeneralizedStrains[point];
|
||||
// x1=y and x2=z: S11=E(epsilon0+x2*kappa_y-x1*kappa_z).
|
||||
recovery.stressPoints.push_back({
|
||||
static_cast<int>(point + 1U),
|
||||
sectionPoint + 1U,
|
||||
x1,
|
||||
x2,
|
||||
youngsModulus_ *
|
||||
(strain[0U] + x2 * strain[2U] - x1 * strain[3U]),
|
||||
"input"});
|
||||
}
|
||||
}
|
||||
return recovery;
|
||||
}
|
||||
return equivalent;
|
||||
}
|
||||
|
||||
EulerBeam3D::EulerBeam3D(
|
||||
double length,
|
||||
double youngsModulus,
|
||||
double shearModulus,
|
||||
double area,
|
||||
double iy,
|
||||
double iz,
|
||||
double torsionalConstant,
|
||||
std::array<double, 9> rotation,
|
||||
std::vector<std::array<double, 2>> sectionPoints)
|
||||
BeamRecovery EulerBeam3D::Recover(
|
||||
const Vector& global_element_displacement) const {
|
||||
const Matrix transform = Transformation(rotation_);
|
||||
const Vector local_displacement =
|
||||
transform.Multiply(global_element_displacement);
|
||||
const auto diagonal = ConstitutiveDiagonal(
|
||||
youngs_modulus_, shear_modulus_, area_, iy_, iz_, torsional_constant_);
|
||||
BeamRecovery recovery{};
|
||||
|
||||
// With parser/CLI distributed loading excluded, Kl*dl is the local outward
|
||||
// end action.
|
||||
const Vector end_action = LocalStiffness().Multiply(local_displacement);
|
||||
for (std::size_t endpoint = 0; endpoint < 2U; ++endpoint) {
|
||||
for (std::size_t component = 0; component < 6U; ++component) {
|
||||
recovery.equilibrium_end_actions[endpoint][component] =
|
||||
end_action[endpoint * 6U + component];
|
||||
}
|
||||
|
||||
const double xi = endpoint == 0U ? -1.0 : 1.0;
|
||||
recovery.endpoint_section_resultants[endpoint] = GeneralizedResultant(
|
||||
GeneralizedStrain(StrainDisplacement(xi, length_), local_displacement),
|
||||
diagonal);
|
||||
}
|
||||
|
||||
const double inverse_sqrt_three = 1.0 / std::sqrt(3.0);
|
||||
const std::array<double, 2> gauss_points = {-inverse_sqrt_three,
|
||||
inverse_sqrt_three};
|
||||
for (std::size_t point = 0; point < gauss_points.size(); ++point) {
|
||||
recovery.gauss_generalized_strains[point] = GeneralizedStrain(
|
||||
StrainDisplacement(gauss_points[point], length_), local_displacement);
|
||||
recovery.gauss_generalized_resultants[point] = GeneralizedResultant(
|
||||
recovery.gauss_generalized_strains[point], diagonal);
|
||||
|
||||
if (section_points_.empty()) {
|
||||
recovery.stress_points.push_back(
|
||||
{static_cast<int>(point + 1U), 0U, 0.0, 0.0,
|
||||
youngs_modulus_ * recovery.gauss_generalized_strains[point][0U],
|
||||
"fesa-default"});
|
||||
continue;
|
||||
}
|
||||
|
||||
for (std::size_t section_point = 0; section_point < section_points_.size();
|
||||
++section_point) {
|
||||
const double x1 = section_points_[section_point][0U];
|
||||
const double x2 = section_points_[section_point][1U];
|
||||
const auto& strain = recovery.gauss_generalized_strains[point];
|
||||
// x1=y and x2=z: S11=E(epsilon0+x2*kappa_y-x1*kappa_z).
|
||||
recovery.stress_points.push_back(
|
||||
{static_cast<int>(point + 1U), section_point + 1U, x1, x2,
|
||||
youngs_modulus_ * (strain[0U] + x2 * strain[2U] - x1 * strain[3U]),
|
||||
"input"});
|
||||
}
|
||||
}
|
||||
return recovery;
|
||||
}
|
||||
|
||||
EulerBeam3D::EulerBeam3D(double length, double youngs_modulus,
|
||||
double shear_modulus, double area, double iy,
|
||||
double iz, double torsional_constant,
|
||||
std::array<double, 9> rotation,
|
||||
std::vector<std::array<double, 2>> section_points)
|
||||
: length_{length},
|
||||
youngsModulus_{youngsModulus},
|
||||
shearModulus_{shearModulus},
|
||||
youngs_modulus_{youngs_modulus},
|
||||
shear_modulus_{shear_modulus},
|
||||
area_{area},
|
||||
iy_{iy},
|
||||
iz_{iz},
|
||||
torsionalConstant_{torsionalConstant},
|
||||
torsional_constant_{torsional_constant},
|
||||
rotation_{rotation},
|
||||
sectionPoints_{std::move(sectionPoints)} {}
|
||||
section_points_{std::move(section_points)} {}
|
||||
|
||||
} // namespace fesa
|
||||
} // namespace fesa
|
||||
|
||||
+793
-857
File diff suppressed because it is too large
Load Diff
@@ -37,16 +37,16 @@ bool tryPositiveInteger(const std::string& text, std::int64_t& value) {
|
||||
|
||||
std::vector<EntityIndex> expandBoundaryTarget(
|
||||
const Domain& domain, const BoundaryCondition& boundary) {
|
||||
for (const auto& set : domain.nodeSets()) {
|
||||
for (const auto& set : domain.NodeSets()) {
|
||||
if (equalName(set.name, boundary.target)) {
|
||||
return set.nodeIndices;
|
||||
return set.node_indices;
|
||||
}
|
||||
}
|
||||
|
||||
std::int64_t sourceLabel = 0;
|
||||
if (tryPositiveInteger(boundary.target, sourceLabel)) {
|
||||
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
|
||||
if (domain.nodes()[node].sourceId.source_label == sourceLabel) {
|
||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||
if (domain.Nodes()[node].source_id.source_label == sourceLabel) {
|
||||
return {static_cast<EntityIndex>(node)};
|
||||
}
|
||||
}
|
||||
@@ -96,15 +96,15 @@ SparsePattern buildSparsePattern(
|
||||
|
||||
Result<DofManager> DofManager::create(const AnalysisModel& model) {
|
||||
const Domain& domain = model.domain();
|
||||
const std::size_t fullCount = domain.nodes().size() * dofsPerNode;
|
||||
const std::size_t fullCount = domain.Nodes().size() * dofsPerNode;
|
||||
|
||||
std::vector<std::optional<double>> prescribedByFullDof(fullCount);
|
||||
for (const EntityIndex boundaryIndex : model.activeBoundaryConditions()) {
|
||||
const auto& boundary = model.step().boundaries.at(boundaryIndex);
|
||||
const auto target = expandBoundaryTarget(domain, boundary);
|
||||
for (const EntityIndex node : target) {
|
||||
for (int component = boundary.firstDof;
|
||||
component <= boundary.lastDof;
|
||||
for (int component = boundary.first_dof;
|
||||
component <= boundary.last_dof;
|
||||
++component) {
|
||||
const std::size_t fullDof =
|
||||
static_cast<std::size_t>(node) * dofsPerNode +
|
||||
@@ -150,12 +150,12 @@ Result<DofManager> DofManager::create(const AnalysisModel& model) {
|
||||
}
|
||||
|
||||
std::vector<std::array<std::size_t, 12>> elementScatters(
|
||||
domain.elements().size());
|
||||
domain.Elements().size());
|
||||
for (const EntityIndex elementIndex : model.activeElements()) {
|
||||
const auto& element = domain.elements().at(elementIndex);
|
||||
const auto& element = domain.Elements().at(elementIndex);
|
||||
auto& scatter = elementScatters.at(elementIndex);
|
||||
for (std::size_t endpoint = 0U; endpoint < element.nodeIndices.size(); ++endpoint) {
|
||||
const std::size_t node = element.nodeIndices[endpoint];
|
||||
for (std::size_t endpoint = 0U; endpoint < element.node_indices.size(); ++endpoint) {
|
||||
const std::size_t node = element.node_indices[endpoint];
|
||||
for (std::size_t component = 0U; component < dofsPerNode; ++component) {
|
||||
scatter[endpoint * dofsPerNode + component] =
|
||||
node * dofsPerNode + component;
|
||||
@@ -164,16 +164,16 @@ Result<DofManager> DofManager::create(const AnalysisModel& model) {
|
||||
}
|
||||
|
||||
std::vector<std::array<std::size_t, 24>> shellElementScatters(
|
||||
domain.shellElements().size());
|
||||
domain.ShellElements().size());
|
||||
for (std::size_t elementIndex = 0U;
|
||||
elementIndex < domain.shellElements().size();
|
||||
elementIndex < domain.ShellElements().size();
|
||||
++elementIndex) {
|
||||
const auto& element = domain.shellElements()[elementIndex];
|
||||
const auto& element = domain.ShellElements()[elementIndex];
|
||||
auto& scatter = shellElementScatters[elementIndex];
|
||||
for (std::size_t nodePosition = 0U;
|
||||
nodePosition < element.nodeIndices.size();
|
||||
nodePosition < element.node_indices.size();
|
||||
++nodePosition) {
|
||||
const std::size_t node = element.nodeIndices[nodePosition];
|
||||
const std::size_t node = element.node_indices[nodePosition];
|
||||
for (std::size_t component = 0U;
|
||||
component < dofsPerNode;
|
||||
++component) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "fesa/io/abaqus/domain_mapper.hpp"
|
||||
|
||||
#include "fesa/model/shell_geometry.hpp"
|
||||
#include "fesa/model/shell_geometry.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -151,7 +151,7 @@ public:
|
||||
failure_->category, {std::move(failure_->diagnostic)}));
|
||||
}
|
||||
SortDiagnostics(definition_.warnings);
|
||||
return Domain::create(std::move(definition_));
|
||||
return Domain::Create(std::move(definition_));
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -419,8 +419,8 @@ private:
|
||||
}
|
||||
|
||||
void parseBlocks() {
|
||||
definition_.sourcePath = input_.sourcePath;
|
||||
definition_.sourceContentIdentity = input_.sourceContentIdentity;
|
||||
definition_.source_path = input_.sourcePath;
|
||||
definition_.source_content_identity = input_.sourceContentIdentity;
|
||||
|
||||
for (std::size_t index = 0U;
|
||||
index < input_.blocks.size() && !failure_;
|
||||
@@ -1776,11 +1776,11 @@ private:
|
||||
if (failure_) {
|
||||
return;
|
||||
}
|
||||
if (!definition_.shellElements.empty()) {
|
||||
auto geometry = preprocessShellGeometry(
|
||||
if (!definition_.shell_elements.empty()) {
|
||||
auto geometry = PreprocessShellGeometry(
|
||||
definition_.nodes,
|
||||
definition_.shellElements,
|
||||
definition_.shellSections);
|
||||
definition_.shell_elements,
|
||||
definition_.shell_sections);
|
||||
if (!geometry.HasValue()) {
|
||||
const auto& status = geometry.GetStatus();
|
||||
failure_ = MappingFailure{
|
||||
@@ -1788,8 +1788,8 @@ private:
|
||||
status.Diagnostics().front()};
|
||||
return;
|
||||
}
|
||||
definition_.shellNodeInitialFrames =
|
||||
std::move(geometry.Value().nodalFrames);
|
||||
definition_.shell_node_initial_frames =
|
||||
std::move(geometry.Value().nodal_frames);
|
||||
}
|
||||
expandAssemblySets();
|
||||
if (failure_) {
|
||||
@@ -1863,10 +1863,10 @@ private:
|
||||
partDefinition.name = part.name;
|
||||
partDefinition.location = part.location;
|
||||
for (const auto& node : part.nodes) {
|
||||
partDefinition.nodeSourceLabels.push_back(node.label);
|
||||
partDefinition.node_source_labels.push_back(node.label);
|
||||
}
|
||||
for (const auto& element : part.elements) {
|
||||
partDefinition.elementSourceLabels.push_back(element.label);
|
||||
partDefinition.element_source_labels.push_back(element.label);
|
||||
for (const auto nodeLabel : element.nodeLabels) {
|
||||
if (findNode(part, nodeLabel) == nullptr) {
|
||||
inputFailure(
|
||||
@@ -1880,7 +1880,7 @@ private:
|
||||
}
|
||||
}
|
||||
for (const auto& set : part.nodeSets) {
|
||||
partDefinition.nodeSetNames.push_back(set.name);
|
||||
partDefinition.node_set_names.push_back(set.name);
|
||||
for (const auto label : set.members) {
|
||||
if (findNode(part, label) == nullptr) {
|
||||
inputFailure(
|
||||
@@ -1892,7 +1892,7 @@ private:
|
||||
}
|
||||
}
|
||||
for (const auto& set : part.elementSets) {
|
||||
partDefinition.elementSetNames.push_back(set.name);
|
||||
partDefinition.element_set_names.push_back(set.name);
|
||||
for (const auto label : set.members) {
|
||||
if (findElement(part, label) == nullptr) {
|
||||
inputFailure(
|
||||
@@ -1919,8 +1919,8 @@ private:
|
||||
return;
|
||||
}
|
||||
const EntityIndex sectionIndex =
|
||||
static_cast<EntityIndex>(definition_.shellSections.size());
|
||||
definition_.shellSections.push_back({
|
||||
static_cast<EntityIndex>(definition_.shell_sections.size());
|
||||
definition_.shell_sections.push_back({
|
||||
section.elementSetName,
|
||||
section.thickness,
|
||||
*materialIndex,
|
||||
@@ -2071,11 +2071,11 @@ private:
|
||||
deltaScaled[2] / lengthRatio};
|
||||
|
||||
const double globalGuideScale =
|
||||
std::max(1.0, maximumAbsolute(section.firstAxis));
|
||||
std::max(1.0, maximumAbsolute(section.first_axis));
|
||||
std::array<double, 3> guideScaled{};
|
||||
for (std::size_t coordinate = 0U; coordinate < 3U; ++coordinate) {
|
||||
guideScaled[coordinate] =
|
||||
section.firstAxis[coordinate] / globalGuideScale;
|
||||
section.first_axis[coordinate] / globalGuideScale;
|
||||
}
|
||||
const double projection =
|
||||
guideScaled[0] * tangent[0] +
|
||||
@@ -2130,7 +2130,7 @@ private:
|
||||
rawNode.coordinates,
|
||||
rawNode.location});
|
||||
nodeIndices.emplace(rawNode.label, index);
|
||||
instance.nodeMappings.push_back({rawNode.label, index});
|
||||
instance.node_mappings.push_back({rawNode.label, index});
|
||||
}
|
||||
|
||||
for (const auto& rawElement : part->elements) {
|
||||
@@ -2199,7 +2199,7 @@ private:
|
||||
"Expanded shell section assignment must resolve exactly once.");
|
||||
return;
|
||||
}
|
||||
if (definition_.shellElements.size() >
|
||||
if (definition_.shell_elements.size() >
|
||||
std::numeric_limits<EntityIndex>::max()) {
|
||||
inputFailure(
|
||||
"entity-index-overflow", rawElement.location,
|
||||
@@ -2208,19 +2208,19 @@ private:
|
||||
return;
|
||||
}
|
||||
index =
|
||||
static_cast<EntityIndex>(definition_.shellElements.size());
|
||||
definition_.shellElements.push_back({
|
||||
static_cast<EntityIndex>(definition_.shell_elements.size());
|
||||
definition_.shell_elements.push_back({
|
||||
{rawInstance.name, rawElement.label, rawElement.labelText},
|
||||
rawElement.type == RawElement::Type::s4
|
||||
? ShellSourceElementType::s4
|
||||
: ShellSourceElementType::s4r,
|
||||
? ShellSourceElementType::kS4
|
||||
: ShellSourceElementType::kS4r,
|
||||
connectedNodes,
|
||||
assignment->second.second,
|
||||
assignment->second.first,
|
||||
rawElement.location});
|
||||
}
|
||||
elementIndices.emplace(rawElement.label, index);
|
||||
instance.elementMappings.push_back({rawElement.label, index});
|
||||
instance.element_mappings.push_back({rawElement.label, index});
|
||||
}
|
||||
|
||||
for (const auto& rawSet : part->nodeSets) {
|
||||
@@ -2234,9 +2234,9 @@ private:
|
||||
"Part node-set expansion failed.");
|
||||
return;
|
||||
}
|
||||
set.nodeIndices.push_back(found->second);
|
||||
set.node_indices.push_back(found->second);
|
||||
}
|
||||
definition_.nodeSets.push_back(std::move(set));
|
||||
definition_.node_sets.push_back(std::move(set));
|
||||
}
|
||||
for (const auto& rawSet : part->elementSets) {
|
||||
ElementSet set{
|
||||
@@ -2250,9 +2250,9 @@ private:
|
||||
"Part element-set expansion failed.");
|
||||
return;
|
||||
}
|
||||
set.elementIndices.push_back(found->second);
|
||||
set.element_indices.push_back(found->second);
|
||||
}
|
||||
definition_.elementSets.push_back(std::move(set));
|
||||
definition_.element_sets.push_back(std::move(set));
|
||||
}
|
||||
definition_.instances.push_back(std::move(instance));
|
||||
}
|
||||
@@ -2281,61 +2281,61 @@ private:
|
||||
return;
|
||||
}
|
||||
if (rawSet.isNodeSet) {
|
||||
definition_.nodeSets.erase(
|
||||
definition_.node_sets.erase(
|
||||
std::remove_if(
|
||||
definition_.nodeSets.begin(),
|
||||
definition_.nodeSets.end(),
|
||||
definition_.node_sets.begin(),
|
||||
definition_.node_sets.end(),
|
||||
[&rawSet](const NodeSet& set) {
|
||||
return equalName(set.name, rawSet.name);
|
||||
}),
|
||||
definition_.nodeSets.end());
|
||||
definition_.node_sets.end());
|
||||
NodeSet set{
|
||||
rawSet.name, definitionInstance->name, {}, rawSet.location};
|
||||
for (const auto member : rawSet.members) {
|
||||
const auto mapping = std::find_if(
|
||||
definitionInstance->nodeMappings.begin(),
|
||||
definitionInstance->nodeMappings.end(),
|
||||
definitionInstance->node_mappings.begin(),
|
||||
definitionInstance->node_mappings.end(),
|
||||
[member](const SourceIndexMapping& value) {
|
||||
return value.sourceLabel == member;
|
||||
return value.source_label == member;
|
||||
});
|
||||
if (mapping == definitionInstance->nodeMappings.end()) {
|
||||
if (mapping == definitionInstance->node_mappings.end()) {
|
||||
inputFailure(
|
||||
"unresolved-reference", rawSet.location,
|
||||
"NSET", rawSet.name,
|
||||
"Assembly node-set member must resolve in its instance.");
|
||||
return;
|
||||
}
|
||||
set.nodeIndices.push_back(mapping->internalIndex);
|
||||
set.node_indices.push_back(mapping->internal_index);
|
||||
}
|
||||
definition_.nodeSets.push_back(std::move(set));
|
||||
definition_.node_sets.push_back(std::move(set));
|
||||
} else {
|
||||
definition_.elementSets.erase(
|
||||
definition_.element_sets.erase(
|
||||
std::remove_if(
|
||||
definition_.elementSets.begin(),
|
||||
definition_.elementSets.end(),
|
||||
definition_.element_sets.begin(),
|
||||
definition_.element_sets.end(),
|
||||
[&rawSet](const ElementSet& set) {
|
||||
return equalName(set.name, rawSet.name);
|
||||
}),
|
||||
definition_.elementSets.end());
|
||||
definition_.element_sets.end());
|
||||
ElementSet set{
|
||||
rawSet.name, definitionInstance->name, {}, rawSet.location};
|
||||
for (const auto member : rawSet.members) {
|
||||
const auto mapping = std::find_if(
|
||||
definitionInstance->elementMappings.begin(),
|
||||
definitionInstance->elementMappings.end(),
|
||||
definitionInstance->element_mappings.begin(),
|
||||
definitionInstance->element_mappings.end(),
|
||||
[member](const SourceIndexMapping& value) {
|
||||
return value.sourceLabel == member;
|
||||
return value.source_label == member;
|
||||
});
|
||||
if (mapping == definitionInstance->elementMappings.end()) {
|
||||
if (mapping == definitionInstance->element_mappings.end()) {
|
||||
inputFailure(
|
||||
"unresolved-reference", rawSet.location,
|
||||
"ELSET", rawSet.name,
|
||||
"Assembly element-set member must resolve in its instance.");
|
||||
return;
|
||||
}
|
||||
set.elementIndices.push_back(mapping->internalIndex);
|
||||
set.element_indices.push_back(mapping->internal_index);
|
||||
}
|
||||
definition_.elementSets.push_back(std::move(set));
|
||||
definition_.element_sets.push_back(std::move(set));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2345,7 +2345,7 @@ private:
|
||||
const SourceLocation& location,
|
||||
const std::string& keyword) {
|
||||
std::vector<const NodeSet*> matchingSets;
|
||||
for (const auto& set : definition_.nodeSets) {
|
||||
for (const auto& set : definition_.node_sets) {
|
||||
if (equalName(set.name, target)) {
|
||||
matchingSets.push_back(&set);
|
||||
}
|
||||
@@ -2357,7 +2357,7 @@ private:
|
||||
for (std::size_t index = 0U;
|
||||
index < definition_.nodes.size();
|
||||
++index) {
|
||||
if (definition_.nodes[index].sourceId.source_label == label) {
|
||||
if (definition_.nodes[index].source_id.source_label == label) {
|
||||
matchingNodes.push_back(static_cast<EntityIndex>(index));
|
||||
}
|
||||
}
|
||||
@@ -2385,7 +2385,7 @@ private:
|
||||
return std::nullopt;
|
||||
}
|
||||
if (matchingSets.size() == 1U) {
|
||||
return matchingSets.front()->nodeIndices;
|
||||
return matchingSets.front()->node_indices;
|
||||
}
|
||||
if (matchingNodes.size() == 1U) {
|
||||
return matchingNodes;
|
||||
@@ -2409,7 +2409,7 @@ private:
|
||||
return;
|
||||
}
|
||||
for (const auto node : *target) {
|
||||
for (int dof = boundary.firstDof; dof <= boundary.lastDof; ++dof) {
|
||||
for (int dof = boundary.first_dof; dof <= boundary.last_dof; ++dof) {
|
||||
const auto key = std::make_pair(node, dof);
|
||||
const auto existing = prescribedValues.find(key);
|
||||
if (existing != prescribedValues.end() &&
|
||||
|
||||
@@ -231,11 +231,11 @@ struct WriterModelData {
|
||||
};
|
||||
|
||||
bool isShellDomain(const Domain& domain) noexcept {
|
||||
return !domain.shellElements().empty();
|
||||
return !domain.ShellElements().empty();
|
||||
}
|
||||
|
||||
const char* shellSourceTypeName(const ShellSourceElementType type) {
|
||||
return type == ShellSourceElementType::s4 ? "S4" : "S4R";
|
||||
return type == ShellSourceElementType::kS4 ? "S4" : "S4R";
|
||||
}
|
||||
|
||||
bool isOrthonormalRightHanded(
|
||||
@@ -266,14 +266,14 @@ bool isOrthonormalRightHanded(
|
||||
|
||||
bool computeLocalAxes(
|
||||
const Domain& domain, const EulerBeam3DDefinition& element, AxisSet& axes) {
|
||||
if (element.nodeIndices[0U] >= domain.nodes().size() ||
|
||||
element.nodeIndices[1U] >= domain.nodes().size() ||
|
||||
element.sectionIndex >= domain.sections().size()) {
|
||||
if (element.node_indices[0U] >= domain.Nodes().size() ||
|
||||
element.node_indices[1U] >= domain.Nodes().size() ||
|
||||
element.section_index >= domain.Sections().size()) {
|
||||
return false;
|
||||
}
|
||||
const auto& first = domain.nodes()[element.nodeIndices[0U]].coordinates;
|
||||
const auto& second = domain.nodes()[element.nodeIndices[1U]].coordinates;
|
||||
const auto& guide = domain.sections()[element.sectionIndex].firstAxis;
|
||||
const auto& first = domain.Nodes()[element.node_indices[0U]].coordinates;
|
||||
const auto& second = domain.Nodes()[element.node_indices[1U]].coordinates;
|
||||
const auto& guide = domain.Sections()[element.section_index].first_axis;
|
||||
const std::array<double, 3> delta = {
|
||||
second[0U] - first[0U],
|
||||
second[1U] - first[1U],
|
||||
@@ -319,48 +319,48 @@ bool sizeProductFits(
|
||||
|
||||
Status validateShellWriterInput(
|
||||
const Domain& domain, const AnalysisState& state) {
|
||||
if (!domain.elements().empty()) {
|
||||
if (!domain.Elements().empty()) {
|
||||
return outputFailure(
|
||||
"invalid-result-identity",
|
||||
"Schema v0 does not combine B33 and FESA-MITC4 element inventories.");
|
||||
}
|
||||
for (const auto& material : domain.materials()) {
|
||||
for (const auto& material : domain.Materials()) {
|
||||
if (material.name.empty() || !isValidUtf8(material.name) ||
|
||||
!std::isfinite(material.youngsModulus) ||
|
||||
!std::isfinite(material.poissonRatio) ||
|
||||
!(material.youngsModulus > 0.0)) {
|
||||
!std::isfinite(material.youngs_modulus) ||
|
||||
!std::isfinite(material.poisson_ratio) ||
|
||||
!(material.youngs_modulus > 0.0)) {
|
||||
return outputFailure(
|
||||
"invalid-result-identity",
|
||||
"Every shell material requires a UTF-8 name and finite constitutive data.");
|
||||
}
|
||||
}
|
||||
for (const auto& section : domain.shellSections()) {
|
||||
for (const auto& section : domain.ShellSections()) {
|
||||
if (section.name.empty() || !isValidUtf8(section.name) ||
|
||||
section.materialIndex >= domain.materials().size() ||
|
||||
section.material_index >= domain.Materials().size() ||
|
||||
!std::isfinite(section.thickness) || !(section.thickness > 0.0)) {
|
||||
return outputFailure(
|
||||
"invalid-result-identity",
|
||||
"Every shell section requires stable material identity and positive finite thickness.");
|
||||
}
|
||||
}
|
||||
for (const auto& element : domain.shellElements()) {
|
||||
if ((element.sourceType != ShellSourceElementType::s4 &&
|
||||
element.sourceType != ShellSourceElementType::s4r) ||
|
||||
element.sourceId.source_label <= 0 ||
|
||||
element.sourceId.source_label_text.empty() ||
|
||||
!isValidUtf8(element.sourceId.instance_name) ||
|
||||
!isValidUtf8(element.sourceId.source_label_text) ||
|
||||
element.materialIndex >= domain.materials().size() ||
|
||||
element.sectionIndex >= domain.shellSections().size() ||
|
||||
domain.shellSections()[element.sectionIndex].materialIndex !=
|
||||
element.materialIndex) {
|
||||
for (const auto& element : domain.ShellElements()) {
|
||||
if ((element.source_type != ShellSourceElementType::kS4 &&
|
||||
element.source_type != ShellSourceElementType::kS4r) ||
|
||||
element.source_id.source_label <= 0 ||
|
||||
element.source_id.source_label_text.empty() ||
|
||||
!isValidUtf8(element.source_id.instance_name) ||
|
||||
!isValidUtf8(element.source_id.source_label_text) ||
|
||||
element.material_index >= domain.Materials().size() ||
|
||||
element.section_index >= domain.ShellSections().size() ||
|
||||
domain.ShellSections()[element.section_index].material_index !=
|
||||
element.material_index) {
|
||||
return outputFailure(
|
||||
"invalid-result-identity",
|
||||
"Every shell element requires stable source, material, and section identity.");
|
||||
}
|
||||
std::array<EntityIndex, kShellNodeCount> sortedNodes = element.nodeIndices;
|
||||
std::array<EntityIndex, kShellNodeCount> sortedNodes = element.node_indices;
|
||||
std::sort(sortedNodes.begin(), sortedNodes.end());
|
||||
if (sortedNodes.back() >= domain.nodes().size() ||
|
||||
if (sortedNodes.back() >= domain.Nodes().size() ||
|
||||
std::adjacent_find(sortedNodes.begin(), sortedNodes.end()) !=
|
||||
sortedNodes.end()) {
|
||||
return outputFailure(
|
||||
@@ -368,18 +368,18 @@ Status validateShellWriterInput(
|
||||
"Every shell element requires four distinct valid node identities.");
|
||||
}
|
||||
}
|
||||
if (domain.shellNodeInitialFrames().size() != domain.nodes().size()) {
|
||||
if (domain.ShellNodeInitialFrames().size() != domain.Nodes().size()) {
|
||||
return outputFailure(
|
||||
"invalid-result-identity",
|
||||
"Shell output requires one initial director/frame per source-ordered node.");
|
||||
}
|
||||
for (std::size_t node = 0U;
|
||||
node < domain.shellNodeInitialFrames().size();
|
||||
node < domain.ShellNodeInitialFrames().size();
|
||||
++node) {
|
||||
const auto& source = domain.shellNodeInitialFrames()[node];
|
||||
const auto& source = domain.ShellNodeInitialFrames()[node];
|
||||
const std::array<std::array<double, 3>, 3> frame{
|
||||
source.tangentA, source.tangentB, source.director};
|
||||
if (source.nodeIndex != node || !isOrthonormalRightHanded(frame)) {
|
||||
source.tangent_a, source.tangent_b, source.director};
|
||||
if (source.node_index != node || !isOrthonormalRightHanded(frame)) {
|
||||
return outputFailure(
|
||||
"invalid-result-identity",
|
||||
"Shell initial frames must be finite, orthonormal, right-handed, and node ordered.");
|
||||
@@ -388,7 +388,7 @@ Status validateShellWriterInput(
|
||||
|
||||
std::size_t expectedRows = 0U;
|
||||
if (!sizeProductFits(
|
||||
domain.shellElements().size(), kShellLocationCount, expectedRows) ||
|
||||
domain.ShellElements().size(), kShellLocationCount, expectedRows) ||
|
||||
state.shellResults().size() != expectedRows) {
|
||||
return outputFailure(
|
||||
"invalid-result-rows",
|
||||
@@ -477,7 +477,7 @@ Status validateWriterInput(
|
||||
}
|
||||
|
||||
std::size_t fullDofCount = 0U;
|
||||
if (!sizeProductFits(domain.nodes().size(), kDofsPerNode, fullDofCount)) {
|
||||
if (!sizeProductFits(domain.Nodes().size(), kDofsPerNode, fullDofCount)) {
|
||||
return outputFailure(
|
||||
"invalid-result-state", "The nodal result shape overflows size_t.");
|
||||
}
|
||||
@@ -502,17 +502,17 @@ Status validateWriterInput(
|
||||
}
|
||||
}
|
||||
|
||||
if (domain.sourcePath().empty() || domain.sourceContentIdentity().empty() ||
|
||||
!isValidUtf8(domain.sourceContentIdentity())) {
|
||||
if (domain.SourcePath().empty() || domain.SourceContentIdentity().empty() ||
|
||||
!isValidUtf8(domain.SourceContentIdentity())) {
|
||||
return outputFailure(
|
||||
"invalid-result-identity",
|
||||
"Source path and UTF-8 content identity are required.");
|
||||
}
|
||||
for (const Node& node : domain.nodes()) {
|
||||
if (node.sourceId.source_label <= 0 ||
|
||||
node.sourceId.source_label_text.empty() ||
|
||||
!isValidUtf8(node.sourceId.instance_name) ||
|
||||
!isValidUtf8(node.sourceId.source_label_text) ||
|
||||
for (const Node& node : domain.Nodes()) {
|
||||
if (node.source_id.source_label <= 0 ||
|
||||
node.source_id.source_label_text.empty() ||
|
||||
!isValidUtf8(node.source_id.instance_name) ||
|
||||
!isValidUtf8(node.source_id.source_label_text) ||
|
||||
!isFinite(node.coordinates)) {
|
||||
return outputFailure(
|
||||
"invalid-result-identity",
|
||||
@@ -521,15 +521,15 @@ Status validateWriterInput(
|
||||
}
|
||||
|
||||
modelData.beamLocalAxes.clear();
|
||||
modelData.beamLocalAxes.reserve(domain.elements().size());
|
||||
for (const EulerBeam3DDefinition& element : domain.elements()) {
|
||||
modelData.beamLocalAxes.reserve(domain.Elements().size());
|
||||
for (const EulerBeam3DDefinition& element : domain.Elements()) {
|
||||
AxisSet axes{};
|
||||
if (element.sourceId.source_label <= 0 ||
|
||||
element.sourceId.source_label_text.empty() ||
|
||||
!isValidUtf8(element.sourceId.instance_name) ||
|
||||
!isValidUtf8(element.sourceId.source_label_text) ||
|
||||
element.nodeIndices[0U] == element.nodeIndices[1U] ||
|
||||
element.materialIndex >= domain.materials().size() ||
|
||||
if (element.source_id.source_label <= 0 ||
|
||||
element.source_id.source_label_text.empty() ||
|
||||
!isValidUtf8(element.source_id.instance_name) ||
|
||||
!isValidUtf8(element.source_id.source_label_text) ||
|
||||
element.node_indices[0U] == element.node_indices[1U] ||
|
||||
element.material_index >= domain.Materials().size() ||
|
||||
!computeLocalAxes(domain, element, axes)) {
|
||||
return outputFailure(
|
||||
"invalid-result-identity",
|
||||
@@ -540,8 +540,8 @@ Status validateWriterInput(
|
||||
|
||||
std::size_t endpointCount = 0U;
|
||||
std::size_t gaussCount = 0U;
|
||||
if (!sizeProductFits(domain.elements().size(), kEndpointCount, endpointCount) ||
|
||||
!sizeProductFits(domain.elements().size(), kGaussPointCount, gaussCount) ||
|
||||
if (!sizeProductFits(domain.Elements().size(), kEndpointCount, endpointCount) ||
|
||||
!sizeProductFits(domain.Elements().size(), kGaussPointCount, gaussCount) ||
|
||||
state.endpointResults().size() != endpointCount ||
|
||||
state.gaussResults().size() != gaussCount) {
|
||||
return outputFailure(
|
||||
@@ -555,11 +555,11 @@ Status validateWriterInput(
|
||||
static_cast<EntityIndex>(rowIndex / kEndpointCount);
|
||||
const int expectedEndpoint = static_cast<int>(rowIndex % kEndpointCount);
|
||||
const EndpointResultRow& row = state.endpointResults()[rowIndex];
|
||||
const auto& element = domain.elements()[expectedElement];
|
||||
const auto& element = domain.Elements()[expectedElement];
|
||||
const auto& expectedNode =
|
||||
domain.nodes()[element.nodeIndices[static_cast<std::size_t>(expectedEndpoint)]];
|
||||
domain.Nodes()[element.node_indices[static_cast<std::size_t>(expectedEndpoint)]];
|
||||
if (row.element != expectedElement || row.endpoint != expectedEndpoint ||
|
||||
!sameIdentity(row.node, expectedNode.sourceId) ||
|
||||
!sameIdentity(row.node, expectedNode.source_id) ||
|
||||
!isFinite(row.endAction) || !isFinite(row.sectionResultant)) {
|
||||
return outputFailure(
|
||||
"invalid-result-rows",
|
||||
@@ -586,11 +586,11 @@ Status validateWriterInput(
|
||||
|
||||
std::size_t stressIndex = 0U;
|
||||
for (std::size_t elementIndex = 0U;
|
||||
elementIndex < domain.elements().size();
|
||||
elementIndex < domain.Elements().size();
|
||||
++elementIndex) {
|
||||
const auto& element = domain.elements()[elementIndex];
|
||||
const auto& element = domain.Elements()[elementIndex];
|
||||
const auto& sectionPoints =
|
||||
domain.sections()[element.sectionIndex].sectionPoints;
|
||||
domain.Sections()[element.section_index].section_points;
|
||||
for (std::size_t gauss = 0U; gauss < kGaussPointCount; ++gauss) {
|
||||
const std::size_t count = sectionPoints.empty() ? 1U : sectionPoints.size();
|
||||
for (std::size_t point = 0U; point < count; ++point) {
|
||||
@@ -681,8 +681,8 @@ std::string normalizedPathString(const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
std::string sourceInputIdentity(const Domain& domain) {
|
||||
return "path=" + normalizedPathString(domain.sourcePath()) +
|
||||
";content_identity=" + domain.sourceContentIdentity();
|
||||
return "path=" + normalizedPathString(domain.SourcePath()) +
|
||||
";content_identity=" + domain.SourceContentIdentity();
|
||||
}
|
||||
|
||||
Hdf5Handle makeUtf8StringType() {
|
||||
@@ -982,13 +982,13 @@ void writeMetadata(const hid_t file, const Domain& domain) {
|
||||
|
||||
void writeNodes(const hid_t file, const Domain& domain) {
|
||||
std::vector<NodeWriteRow> rows;
|
||||
rows.reserve(domain.nodes().size());
|
||||
for (std::size_t index = 0U; index < domain.nodes().size(); ++index) {
|
||||
const Node& node = domain.nodes()[index];
|
||||
rows.reserve(domain.Nodes().size());
|
||||
for (std::size_t index = 0U; index < domain.Nodes().size(); ++index) {
|
||||
const Node& node = domain.Nodes()[index];
|
||||
rows.push_back({
|
||||
static_cast<std::uint64_t>(index),
|
||||
node.sourceId.instance_name.c_str(),
|
||||
node.sourceId.source_label_text.c_str(),
|
||||
node.source_id.instance_name.c_str(),
|
||||
node.source_id.source_label_text.c_str(),
|
||||
{node.coordinates[0U], node.coordinates[1U], node.coordinates[2U]}});
|
||||
}
|
||||
|
||||
@@ -1057,15 +1057,15 @@ void writeNodes(const hid_t file, const Domain& domain) {
|
||||
void writeBeamElements(
|
||||
const hid_t file, const Domain& domain, const std::vector<AxisSet>& axes) {
|
||||
std::vector<ElementWriteRow> rows;
|
||||
rows.reserve(domain.elements().size());
|
||||
for (std::size_t index = 0U; index < domain.elements().size(); ++index) {
|
||||
const auto& element = domain.elements()[index];
|
||||
rows.reserve(domain.Elements().size());
|
||||
for (std::size_t index = 0U; index < domain.Elements().size(); ++index) {
|
||||
const auto& element = domain.Elements()[index];
|
||||
ElementWriteRow row{
|
||||
static_cast<std::uint64_t>(index),
|
||||
element.sourceId.instance_name.c_str(),
|
||||
element.sourceId.source_label_text.c_str(),
|
||||
{static_cast<std::uint64_t>(element.nodeIndices[0U]),
|
||||
static_cast<std::uint64_t>(element.nodeIndices[1U])},
|
||||
element.source_id.instance_name.c_str(),
|
||||
element.source_id.source_label_text.c_str(),
|
||||
{static_cast<std::uint64_t>(element.node_indices[0U]),
|
||||
static_cast<std::uint64_t>(element.node_indices[1U])},
|
||||
{}};
|
||||
std::copy(axes[index].begin(), axes[index].end(), row.localAxes);
|
||||
rows.push_back(row);
|
||||
@@ -1141,21 +1141,21 @@ void writeBeamElements(
|
||||
|
||||
void writeShellElements(const hid_t file, const Domain& domain) {
|
||||
std::vector<ShellElementWriteRow> rows;
|
||||
rows.reserve(domain.shellElements().size());
|
||||
for (std::size_t index = 0U; index < domain.shellElements().size(); ++index) {
|
||||
const auto& element = domain.shellElements()[index];
|
||||
rows.reserve(domain.ShellElements().size());
|
||||
for (std::size_t index = 0U; index < domain.ShellElements().size(); ++index) {
|
||||
const auto& element = domain.ShellElements()[index];
|
||||
rows.push_back({
|
||||
static_cast<std::uint64_t>(index),
|
||||
element.sourceId.instance_name.c_str(),
|
||||
element.sourceId.source_label_text.c_str(),
|
||||
shellSourceTypeName(element.sourceType),
|
||||
element.source_id.instance_name.c_str(),
|
||||
element.source_id.source_label_text.c_str(),
|
||||
shellSourceTypeName(element.source_type),
|
||||
kMitc4InternalFormulation.data(),
|
||||
{static_cast<std::uint64_t>(element.nodeIndices[0U]),
|
||||
static_cast<std::uint64_t>(element.nodeIndices[1U]),
|
||||
static_cast<std::uint64_t>(element.nodeIndices[2U]),
|
||||
static_cast<std::uint64_t>(element.nodeIndices[3U])},
|
||||
static_cast<std::uint64_t>(element.sectionIndex),
|
||||
static_cast<std::uint64_t>(element.materialIndex)});
|
||||
{static_cast<std::uint64_t>(element.node_indices[0U]),
|
||||
static_cast<std::uint64_t>(element.node_indices[1U]),
|
||||
static_cast<std::uint64_t>(element.node_indices[2U]),
|
||||
static_cast<std::uint64_t>(element.node_indices[3U])},
|
||||
static_cast<std::uint64_t>(element.section_index),
|
||||
static_cast<std::uint64_t>(element.material_index)});
|
||||
}
|
||||
|
||||
auto stringType = makeUtf8StringType();
|
||||
@@ -1226,14 +1226,14 @@ void writeShellElements(const hid_t file, const Domain& domain) {
|
||||
|
||||
void writeShellMaterials(const hid_t file, const Domain& domain) {
|
||||
std::vector<ShellMaterialWriteRow> rows;
|
||||
rows.reserve(domain.materials().size());
|
||||
for (std::size_t index = 0U; index < domain.materials().size(); ++index) {
|
||||
const auto& material = domain.materials()[index];
|
||||
rows.reserve(domain.Materials().size());
|
||||
for (std::size_t index = 0U; index < domain.Materials().size(); ++index) {
|
||||
const auto& material = domain.Materials()[index];
|
||||
rows.push_back({
|
||||
static_cast<std::uint64_t>(index),
|
||||
material.name.c_str(),
|
||||
material.youngsModulus,
|
||||
material.poissonRatio});
|
||||
material.youngs_modulus,
|
||||
material.poisson_ratio});
|
||||
}
|
||||
auto stringType = makeUtf8StringType();
|
||||
Hdf5Handle fileType{
|
||||
@@ -1276,20 +1276,20 @@ void writeShellMaterials(const hid_t file, const Domain& domain) {
|
||||
|
||||
void writeShellSections(const hid_t file, const Domain& domain) {
|
||||
std::vector<std::string> sourceFiles;
|
||||
sourceFiles.reserve(domain.shellSections().size());
|
||||
for (const auto& section : domain.shellSections()) {
|
||||
sourceFiles.reserve(domain.ShellSections().size());
|
||||
for (const auto& section : domain.ShellSections()) {
|
||||
sourceFiles.push_back(normalizedPathString(section.location.file));
|
||||
}
|
||||
std::vector<ShellSectionWriteRow> rows;
|
||||
rows.reserve(domain.shellSections().size());
|
||||
for (std::size_t index = 0U; index < domain.shellSections().size(); ++index) {
|
||||
const auto& section = domain.shellSections()[index];
|
||||
rows.reserve(domain.ShellSections().size());
|
||||
for (std::size_t index = 0U; index < domain.ShellSections().size(); ++index) {
|
||||
const auto& section = domain.ShellSections()[index];
|
||||
rows.push_back({
|
||||
static_cast<std::uint64_t>(index),
|
||||
sourceFiles[index].c_str(),
|
||||
static_cast<std::uint64_t>(section.location.line),
|
||||
section.name.c_str(),
|
||||
static_cast<std::uint64_t>(section.materialIndex),
|
||||
static_cast<std::uint64_t>(section.material_index),
|
||||
section.thickness});
|
||||
}
|
||||
auto stringType = makeUtf8StringType();
|
||||
@@ -1343,24 +1343,24 @@ void writeShellSections(const hid_t file, const Domain& domain) {
|
||||
void writeShellModelData(
|
||||
const hid_t file, const Domain& domain, const WriterModelData& modelData) {
|
||||
std::vector<double> directors;
|
||||
directors.reserve(domain.nodes().size() * 3U);
|
||||
directors.reserve(domain.Nodes().size() * 3U);
|
||||
std::vector<double> frames;
|
||||
frames.reserve(domain.nodes().size() * 9U);
|
||||
for (const auto& frame : domain.shellNodeInitialFrames()) {
|
||||
frames.reserve(domain.Nodes().size() * 9U);
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
directors.insert(
|
||||
directors.end(), frame.director.begin(), frame.director.end());
|
||||
frames.insert(frames.end(), frame.tangentA.begin(), frame.tangentA.end());
|
||||
frames.insert(frames.end(), frame.tangentB.begin(), frame.tangentB.end());
|
||||
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
|
||||
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
|
||||
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
|
||||
}
|
||||
writeModelDoubleDataset(
|
||||
file, "/model/shell/nodal_director",
|
||||
{static_cast<hsize_t>(domain.nodes().size()), 3U},
|
||||
{static_cast<hsize_t>(domain.Nodes().size()), 3U},
|
||||
directors.data(), directors.size(), "D1,D2,D3", "1,1,1",
|
||||
"global-cartesian", "nodal");
|
||||
writeModelDoubleDataset(
|
||||
file, "/model/shell/nodal_frame",
|
||||
{static_cast<hsize_t>(domain.nodes().size()), 3U, 3U},
|
||||
{static_cast<hsize_t>(domain.Nodes().size()), 3U, 3U},
|
||||
frames.data(), frames.size(), "X,Y,Z", "1,1,1",
|
||||
"global-cartesian", "nodal-frame");
|
||||
{
|
||||
@@ -1375,7 +1375,7 @@ void writeShellModelData(
|
||||
writeShellMaterials(file, domain);
|
||||
writeShellSections(file, domain);
|
||||
const std::vector<hsize_t> nodalDimensions{
|
||||
static_cast<hsize_t>(domain.nodes().size()), kDofsPerNode};
|
||||
static_cast<hsize_t>(domain.Nodes().size()), kDofsPerNode};
|
||||
writeUint8Dataset(
|
||||
file, "/model/nodal_constraint_mask", nodalDimensions,
|
||||
modelData.constraintMask.data(), modelData.constraintMask.size());
|
||||
@@ -1573,7 +1573,7 @@ void writeShellResultDatasets(
|
||||
}
|
||||
|
||||
const hsize_t elementCount =
|
||||
static_cast<hsize_t>(domain.shellElements().size());
|
||||
static_cast<hsize_t>(domain.ShellElements().size());
|
||||
const std::string root = std::string{kStepRoot} + "/element/shell";
|
||||
const std::string framePath = root + "/local_frame";
|
||||
writeDoubleDataset(
|
||||
@@ -1730,7 +1730,7 @@ void writeDiagnostics(
|
||||
void writeResultDatasets(
|
||||
const hid_t file, const Domain& domain, const AnalysisState& state) {
|
||||
const std::vector<hsize_t> nodalDimensions = {
|
||||
static_cast<hsize_t>(domain.nodes().size()), kDofsPerNode};
|
||||
static_cast<hsize_t>(domain.Nodes().size()), kDofsPerNode};
|
||||
writeDoubleDataset(
|
||||
file,
|
||||
std::string{kStepRoot} + "/nodal/displacement",
|
||||
@@ -1758,11 +1758,11 @@ void writeResultDatasets(
|
||||
}
|
||||
|
||||
const std::vector<hsize_t> endpointActionDimensions = {
|
||||
static_cast<hsize_t>(domain.elements().size()),
|
||||
static_cast<hsize_t>(domain.Elements().size()),
|
||||
kEndpointCount,
|
||||
kEndActionComponentCount};
|
||||
const std::vector<hsize_t> generalizedDimensions = {
|
||||
static_cast<hsize_t>(domain.elements().size()),
|
||||
static_cast<hsize_t>(domain.Elements().size()),
|
||||
kEndpointCount,
|
||||
kGeneralizedComponentCount};
|
||||
const auto endActions = flattenEndpointValues(state.endpointResults(), false);
|
||||
@@ -2287,13 +2287,13 @@ void selfCheckFile(
|
||||
requireCompoundDataset(
|
||||
file.get(),
|
||||
"/model/nodes",
|
||||
static_cast<hsize_t>(domain.nodes().size()),
|
||||
static_cast<hsize_t>(domain.Nodes().size()),
|
||||
{"internal_node_id", "instance_name", "source_label", "coordinates"});
|
||||
auto nodes = openDatasetForCheck(file.get(), "/model/nodes");
|
||||
requireStringAttribute(nodes.get(), "coordinate_system", "global-cartesian");
|
||||
requireStringAttribute(nodes.get(), "units_label", "length");
|
||||
const std::vector<hsize_t> nodalDimensions = {
|
||||
static_cast<hsize_t>(domain.nodes().size()), kDofsPerNode};
|
||||
static_cast<hsize_t>(domain.Nodes().size()), kDofsPerNode};
|
||||
requireDoubleDataset(
|
||||
file.get(),
|
||||
"/steps/Step-1/frames/0/nodal/displacement",
|
||||
@@ -2315,7 +2315,7 @@ void selfCheckFile(
|
||||
requireCompoundDataset(
|
||||
file.get(),
|
||||
"/model/elements",
|
||||
static_cast<hsize_t>(domain.shellElements().size()),
|
||||
static_cast<hsize_t>(domain.ShellElements().size()),
|
||||
{"internal_element_id", "instance_name", "source_label",
|
||||
"source_element_type", "internal_formulation", "node_internal_ids",
|
||||
"shell_section_internal_id", "material_internal_id"});
|
||||
@@ -2324,33 +2324,33 @@ void selfCheckFile(
|
||||
requireCompoundDataset(
|
||||
file.get(),
|
||||
"/model/shell/materials",
|
||||
static_cast<hsize_t>(domain.materials().size()),
|
||||
static_cast<hsize_t>(domain.Materials().size()),
|
||||
{"internal_material_id", "name", "E", "nu"});
|
||||
requireCompoundDataset(
|
||||
file.get(),
|
||||
"/model/shell/sections",
|
||||
static_cast<hsize_t>(domain.shellSections().size()),
|
||||
static_cast<hsize_t>(domain.ShellSections().size()),
|
||||
{"internal_section_id", "source_file", "source_line", "source_elset",
|
||||
"material_internal_id", "thickness"});
|
||||
|
||||
std::vector<double> directors;
|
||||
std::vector<double> frames;
|
||||
directors.reserve(domain.nodes().size() * 3U);
|
||||
frames.reserve(domain.nodes().size() * 9U);
|
||||
for (const auto& frame : domain.shellNodeInitialFrames()) {
|
||||
directors.reserve(domain.Nodes().size() * 3U);
|
||||
frames.reserve(domain.Nodes().size() * 9U);
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
directors.insert(
|
||||
directors.end(), frame.director.begin(), frame.director.end());
|
||||
frames.insert(frames.end(), frame.tangentA.begin(), frame.tangentA.end());
|
||||
frames.insert(frames.end(), frame.tangentB.begin(), frame.tangentB.end());
|
||||
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
|
||||
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
|
||||
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
|
||||
}
|
||||
requireModelDoubleDataset(
|
||||
file.get(), "/model/shell/nodal_director",
|
||||
{static_cast<hsize_t>(domain.nodes().size()), 3U},
|
||||
{static_cast<hsize_t>(domain.Nodes().size()), 3U},
|
||||
"D1,D2,D3", "1,1,1", "global-cartesian", "nodal", &directors);
|
||||
requireModelDoubleDataset(
|
||||
file.get(), "/model/shell/nodal_frame",
|
||||
{static_cast<hsize_t>(domain.nodes().size()), 3U, 3U},
|
||||
{static_cast<hsize_t>(domain.Nodes().size()), 3U, 3U},
|
||||
"X,Y,Z", "1,1,1", "global-cartesian", "nodal-frame", &frames);
|
||||
auto nodalFrame = openDatasetForCheck(
|
||||
file.get(), "/model/shell/nodal_frame");
|
||||
@@ -2383,7 +2383,7 @@ void selfCheckFile(
|
||||
positions.get(), "position_names", "BOTTOM,MIDDLE,TOP");
|
||||
|
||||
const hsize_t elementCount =
|
||||
static_cast<hsize_t>(domain.shellElements().size());
|
||||
static_cast<hsize_t>(domain.ShellElements().size());
|
||||
const std::string shellRoot = std::string{kStepRoot} + "/element/shell";
|
||||
requireDoubleDataset(
|
||||
file.get(), (shellRoot + "/local_frame").c_str(),
|
||||
@@ -2459,18 +2459,18 @@ void selfCheckFile(
|
||||
requireCompoundDataset(
|
||||
file.get(),
|
||||
"/model/elements",
|
||||
static_cast<hsize_t>(domain.elements().size()),
|
||||
static_cast<hsize_t>(domain.Elements().size()),
|
||||
{"internal_element_id", "instance_name", "source_label",
|
||||
"node_internal_ids", "local_axes"});
|
||||
auto elements = openDatasetForCheck(file.get(), "/model/elements");
|
||||
requireStringAttribute(
|
||||
elements.get(), "formulation", "B33-3D-Euler-Bernoulli");
|
||||
const std::vector<hsize_t> endDimensions = {
|
||||
static_cast<hsize_t>(domain.elements().size()),
|
||||
static_cast<hsize_t>(domain.Elements().size()),
|
||||
kEndpointCount,
|
||||
kEndActionComponentCount};
|
||||
const std::vector<hsize_t> generalizedDimensions = {
|
||||
static_cast<hsize_t>(domain.elements().size()),
|
||||
static_cast<hsize_t>(domain.Elements().size()),
|
||||
kGaussPointCount,
|
||||
kGeneralizedComponentCount};
|
||||
requireDoubleDataset(
|
||||
|
||||
+32
-30
@@ -1,66 +1,68 @@
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace fesa {
|
||||
|
||||
Result<Domain> Domain::create(ModelDefinition definition) {
|
||||
return Result<Domain>::Success(Domain{std::move(definition)});
|
||||
Result<Domain> Domain::Create(ModelDefinition definition) {
|
||||
return Result<Domain>::Success(Domain{std::move(definition)});
|
||||
}
|
||||
|
||||
const std::vector<Node>& Domain::nodes() const noexcept {
|
||||
return definition_.nodes;
|
||||
const std::vector<Node>& Domain::Nodes() const noexcept {
|
||||
return definition_.nodes;
|
||||
}
|
||||
|
||||
const std::vector<EulerBeam3DDefinition>& Domain::elements() const noexcept {
|
||||
return definition_.elements;
|
||||
const std::vector<EulerBeam3DDefinition>& Domain::Elements() const noexcept {
|
||||
return definition_.elements;
|
||||
}
|
||||
|
||||
const std::vector<Mitc4ShellDefinition>& Domain::shellElements() const noexcept {
|
||||
return definition_.shellElements;
|
||||
const std::vector<Mitc4ShellDefinition>& Domain::ShellElements()
|
||||
const noexcept {
|
||||
return definition_.shell_elements;
|
||||
}
|
||||
|
||||
const std::vector<LinearElasticMaterial>& Domain::materials() const noexcept {
|
||||
return definition_.materials;
|
||||
const std::vector<LinearElasticMaterial>& Domain::Materials() const noexcept {
|
||||
return definition_.materials;
|
||||
}
|
||||
|
||||
const std::vector<GeneralBeamSection>& Domain::sections() const noexcept {
|
||||
return definition_.sections;
|
||||
const std::vector<GeneralBeamSection>& Domain::Sections() const noexcept {
|
||||
return definition_.sections;
|
||||
}
|
||||
|
||||
const std::vector<ShellSection>& Domain::shellSections() const noexcept {
|
||||
return definition_.shellSections;
|
||||
const std::vector<ShellSection>& Domain::ShellSections() const noexcept {
|
||||
return definition_.shell_sections;
|
||||
}
|
||||
|
||||
const std::vector<ShellNodeInitialFrame>& Domain::shellNodeInitialFrames() const noexcept {
|
||||
return definition_.shellNodeInitialFrames;
|
||||
const std::vector<ShellNodeInitialFrame>& Domain::ShellNodeInitialFrames()
|
||||
const noexcept {
|
||||
return definition_.shell_node_initial_frames;
|
||||
}
|
||||
|
||||
const std::vector<NodeSet>& Domain::nodeSets() const noexcept {
|
||||
return definition_.nodeSets;
|
||||
const std::vector<NodeSet>& Domain::NodeSets() const noexcept {
|
||||
return definition_.node_sets;
|
||||
}
|
||||
|
||||
const std::vector<ElementSet>& Domain::elementSets() const noexcept {
|
||||
return definition_.elementSets;
|
||||
const std::vector<ElementSet>& Domain::ElementSets() const noexcept {
|
||||
return definition_.element_sets;
|
||||
}
|
||||
|
||||
const std::vector<StaticStepDefinition>& Domain::steps() const noexcept {
|
||||
return definition_.steps;
|
||||
const std::vector<StaticStepDefinition>& Domain::Steps() const noexcept {
|
||||
return definition_.steps;
|
||||
}
|
||||
|
||||
const std::vector<Diagnostic>& Domain::warnings() const noexcept {
|
||||
return definition_.warnings;
|
||||
const std::vector<Diagnostic>& Domain::Warnings() const noexcept {
|
||||
return definition_.warnings;
|
||||
}
|
||||
|
||||
const std::filesystem::path& Domain::sourcePath() const noexcept {
|
||||
return definition_.sourcePath;
|
||||
const std::filesystem::path& Domain::SourcePath() const noexcept {
|
||||
return definition_.source_path;
|
||||
}
|
||||
|
||||
const std::string& Domain::sourceContentIdentity() const noexcept {
|
||||
return definition_.sourceContentIdentity;
|
||||
const std::string& Domain::SourceContentIdentity() const noexcept {
|
||||
return definition_.source_content_identity;
|
||||
}
|
||||
|
||||
Domain::Domain(ModelDefinition definition)
|
||||
: definition_{std::move(definition)} {}
|
||||
|
||||
} // namespace fesa
|
||||
} // namespace fesa
|
||||
|
||||
+379
-413
@@ -1,4 +1,4 @@
|
||||
#include "fesa/model/shell_geometry.hpp"
|
||||
#include "fesa/model/shell_geometry.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -14,478 +14,444 @@ constexpr std::array<double, 4> kXiSigns{-1.0, 1.0, 1.0, -1.0};
|
||||
constexpr std::array<double, 4> kEtaSigns{-1.0, -1.0, 1.0, 1.0};
|
||||
|
||||
struct ShapeData {
|
||||
std::array<double, 4> values;
|
||||
std::array<double, 4> xiDerivatives;
|
||||
std::array<double, 4> etaDerivatives;
|
||||
std::array<double, 4> values;
|
||||
std::array<double, 4> xi_derivatives;
|
||||
std::array<double, 4> eta_derivatives;
|
||||
};
|
||||
|
||||
struct ElementWork {
|
||||
std::array<Vector3, 4> coordinates;
|
||||
Vector3 normal;
|
||||
double areaWeight;
|
||||
std::array<Vector3, 4> coordinates;
|
||||
Vector3 normal;
|
||||
double area_weight;
|
||||
};
|
||||
|
||||
Vector3 add(const Vector3& left, const Vector3& right) {
|
||||
return {
|
||||
left[0] + right[0], left[1] + right[1], left[2] + right[2]};
|
||||
Vector3 Add(const Vector3& left, const Vector3& right) {
|
||||
return {left[0] + right[0], left[1] + right[1], left[2] + right[2]};
|
||||
}
|
||||
|
||||
Vector3 subtract(const Vector3& left, const Vector3& right) {
|
||||
return {
|
||||
left[0] - right[0], left[1] - right[1], left[2] - right[2]};
|
||||
Vector3 Subtract(const Vector3& left, const Vector3& right) {
|
||||
return {left[0] - right[0], left[1] - right[1], left[2] - right[2]};
|
||||
}
|
||||
|
||||
Vector3 scale(double factor, const Vector3& value) {
|
||||
return {factor * value[0], factor * value[1], factor * value[2]};
|
||||
Vector3 Scale(double factor, const Vector3& value) {
|
||||
return {factor * value[0], factor * value[1], factor * value[2]};
|
||||
}
|
||||
|
||||
double dot(const Vector3& left, const Vector3& right) {
|
||||
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
|
||||
double Dot(const Vector3& left, const Vector3& right) {
|
||||
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
|
||||
}
|
||||
|
||||
Vector3 cross(const Vector3& left, const Vector3& right) {
|
||||
return {
|
||||
left[1] * right[2] - left[2] * right[1],
|
||||
left[2] * right[0] - left[0] * right[2],
|
||||
left[0] * right[1] - left[1] * right[0]};
|
||||
Vector3 Cross(const Vector3& left, const Vector3& right) {
|
||||
return {left[1] * right[2] - left[2] * right[1],
|
||||
left[2] * right[0] - left[0] * right[2],
|
||||
left[0] * right[1] - left[1] * right[0]};
|
||||
}
|
||||
|
||||
double norm(const Vector3& value) {
|
||||
return std::hypot(value[0], value[1], value[2]);
|
||||
double Norm(const Vector3& value) {
|
||||
return std::hypot(value[0], value[1], value[2]);
|
||||
}
|
||||
|
||||
bool isFinite(const Vector3& value) {
|
||||
return std::all_of(value.begin(), value.end(), [](double component) {
|
||||
return std::isfinite(component);
|
||||
});
|
||||
bool IsFinite(const Vector3& value) {
|
||||
return std::all_of(value.begin(), value.end(),
|
||||
[](double component) { return std::isfinite(component); });
|
||||
}
|
||||
|
||||
ShapeData shapeData(double xi, double eta) {
|
||||
ShapeData data{};
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
data.values[node] =
|
||||
0.25 * (1.0 + kXiSigns[node] * xi) *
|
||||
(1.0 + kEtaSigns[node] * eta);
|
||||
data.xiDerivatives[node] =
|
||||
0.25 * kXiSigns[node] * (1.0 + kEtaSigns[node] * eta);
|
||||
data.etaDerivatives[node] =
|
||||
0.25 * kEtaSigns[node] * (1.0 + kXiSigns[node] * xi);
|
||||
}
|
||||
return data;
|
||||
ShapeData ShapeDataAt(double xi, double eta) {
|
||||
ShapeData data{};
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
data.values[node] =
|
||||
0.25 * (1.0 + kXiSigns[node] * xi) * (1.0 + kEtaSigns[node] * eta);
|
||||
data.xi_derivatives[node] =
|
||||
0.25 * kXiSigns[node] * (1.0 + kEtaSigns[node] * eta);
|
||||
data.eta_derivatives[node] =
|
||||
0.25 * kEtaSigns[node] * (1.0 + kXiSigns[node] * xi);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
Vector3 weightedSum(
|
||||
const std::array<double, 4>& weights,
|
||||
const std::array<Vector3, 4>& values) {
|
||||
Vector3 result{};
|
||||
for (std::size_t node = 0U; node < values.size(); ++node) {
|
||||
result = add(result, scale(weights[node], values[node]));
|
||||
}
|
||||
return result;
|
||||
Vector3 WeightedSum(const std::array<double, 4>& weights,
|
||||
const std::array<Vector3, 4>& values) {
|
||||
Vector3 result{};
|
||||
for (std::size_t node = 0U; node < values.size(); ++node) {
|
||||
result = Add(result, Scale(weights[node], values[node]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Vector3 derivativeSum(
|
||||
const std::array<double, 4>& derivatives,
|
||||
const std::array<Vector3, 4>& coordinates) {
|
||||
// Shape derivatives sum to zero, so translating by node 1 improves the
|
||||
// numerical cancellation without changing the covariant tangent.
|
||||
std::array<Vector3, 4> relative{};
|
||||
for (std::size_t node = 0U; node < coordinates.size(); ++node) {
|
||||
relative[node] = subtract(coordinates[node], coordinates[0]);
|
||||
}
|
||||
return weightedSum(derivatives, relative);
|
||||
/// @brief Forms a translation-invariant covariant tangent in node order.
|
||||
Vector3 DerivativeSum(const std::array<double, 4>& derivatives,
|
||||
const std::array<Vector3, 4>& coordinates) {
|
||||
// Shape derivatives sum to zero, so translating by node 1 improves the
|
||||
// numerical cancellation without changing the covariant tangent.
|
||||
std::array<Vector3, 4> relative{};
|
||||
for (std::size_t node = 0U; node < coordinates.size(); ++node) {
|
||||
relative[node] = Subtract(coordinates[node], coordinates[0]);
|
||||
}
|
||||
return WeightedSum(derivatives, relative);
|
||||
}
|
||||
|
||||
Result<ShellGeometry> geometryFailure(
|
||||
std::string code,
|
||||
const SourceLocation& location,
|
||||
std::string keyword,
|
||||
std::string identity,
|
||||
std::string message) {
|
||||
return Result<ShellGeometry>::Failure(Status::Failure(
|
||||
FailureCategory::kModel,
|
||||
{{Severity::kError,
|
||||
std::move(code),
|
||||
location,
|
||||
std::move(keyword),
|
||||
std::move(identity),
|
||||
std::move(message)}}));
|
||||
Result<ShellGeometry> GeometryFailure(std::string code,
|
||||
const SourceLocation& location,
|
||||
std::string keyword, std::string identity,
|
||||
std::string message) {
|
||||
return Result<ShellGeometry>::Failure(Status::Failure(
|
||||
FailureCategory::kModel,
|
||||
{{Severity::kError, std::move(code), location, std::move(keyword),
|
||||
std::move(identity), std::move(message)}}));
|
||||
}
|
||||
|
||||
bool sameCoordinates(const Vector3& left, const Vector3& right) {
|
||||
return left == right;
|
||||
bool SameCoordinates(const Vector3& left, const Vector3& right) {
|
||||
return left == right;
|
||||
}
|
||||
|
||||
double orientation(
|
||||
const Vector3& first,
|
||||
const Vector3& second,
|
||||
const Vector3& third,
|
||||
const Vector3& normal) {
|
||||
return dot(cross(subtract(second, first), subtract(third, first)), normal);
|
||||
double Orientation(const Vector3& first, const Vector3& second,
|
||||
const Vector3& third, const Vector3& normal) {
|
||||
return Dot(Cross(Subtract(second, first), Subtract(third, first)), normal);
|
||||
}
|
||||
|
||||
bool hasOppositeSigns(double first, double second) {
|
||||
return (first < 0.0 && second > 0.0) ||
|
||||
(first > 0.0 && second < 0.0);
|
||||
bool HasOppositeSigns(double first, double second) {
|
||||
return (first < 0.0 && second > 0.0) || (first > 0.0 && second < 0.0);
|
||||
}
|
||||
|
||||
bool segmentsProperlyIntersect(
|
||||
const Vector3& firstStart,
|
||||
const Vector3& firstEnd,
|
||||
const Vector3& secondStart,
|
||||
const Vector3& secondEnd,
|
||||
const Vector3& normal) {
|
||||
const double firstSideStart =
|
||||
orientation(firstStart, firstEnd, secondStart, normal);
|
||||
const double firstSideEnd =
|
||||
orientation(firstStart, firstEnd, secondEnd, normal);
|
||||
const double secondSideStart =
|
||||
orientation(secondStart, secondEnd, firstStart, normal);
|
||||
const double secondSideEnd =
|
||||
orientation(secondStart, secondEnd, firstEnd, normal);
|
||||
return hasOppositeSigns(firstSideStart, firstSideEnd) &&
|
||||
hasOppositeSigns(secondSideStart, secondSideEnd);
|
||||
/// @brief Detects a proper projected edge crossing without a tolerance clamp.
|
||||
bool SegmentsProperlyIntersect(const Vector3& first_start,
|
||||
const Vector3& first_end,
|
||||
const Vector3& second_start,
|
||||
const Vector3& second_end,
|
||||
const Vector3& normal) {
|
||||
const double first_side_start =
|
||||
Orientation(first_start, first_end, second_start, normal);
|
||||
const double first_side_end =
|
||||
Orientation(first_start, first_end, second_end, normal);
|
||||
const double second_side_start =
|
||||
Orientation(second_start, second_end, first_start, normal);
|
||||
const double second_side_end =
|
||||
Orientation(second_start, second_end, first_end, normal);
|
||||
return HasOppositeSigns(first_side_start, first_side_end) &&
|
||||
HasOppositeSigns(second_side_start, second_side_end);
|
||||
}
|
||||
|
||||
bool sourceIdentityLess(
|
||||
const Mitc4ShellDefinition& left,
|
||||
std::size_t leftIndex,
|
||||
const Mitc4ShellDefinition& right,
|
||||
std::size_t rightIndex) {
|
||||
return std::tie(
|
||||
left.sourceId.instance_name,
|
||||
left.sourceId.source_label,
|
||||
left.sourceId.source_label_text,
|
||||
leftIndex) <
|
||||
std::tie(
|
||||
right.sourceId.instance_name,
|
||||
right.sourceId.source_label,
|
||||
right.sourceId.source_label_text,
|
||||
rightIndex);
|
||||
/// @brief Orders incident shell contributions by stable source identity.
|
||||
bool SourceIdentityLess(const Mitc4ShellDefinition& left,
|
||||
std::size_t left_index,
|
||||
const Mitc4ShellDefinition& right,
|
||||
std::size_t right_index) {
|
||||
return std::tie(left.source_id.instance_name, left.source_id.source_label,
|
||||
left.source_id.source_label_text, left_index) <
|
||||
std::tie(right.source_id.instance_name, right.source_id.source_label,
|
||||
right.source_id.source_label_text, right_index);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
const std::array<ShellGeometryValidationPoint, 17>&
|
||||
shellGeometryValidationPoints() noexcept {
|
||||
static const std::array<ShellGeometryValidationPoint, 17> points = [] {
|
||||
const double g = 1.0 / std::sqrt(3.0);
|
||||
return std::array<ShellGeometryValidationPoint, 17>{
|
||||
ShellGeometryValidationPoint{
|
||||
ShellGeometryPointKind::center, 0U, {0.0, 0.0, 0.0}},
|
||||
{ShellGeometryPointKind::stiffness, 0U, {-g, -g, -g}},
|
||||
{ShellGeometryPointKind::stiffness, 1U, {-g, -g, g}},
|
||||
{ShellGeometryPointKind::stiffness, 2U, {g, -g, -g}},
|
||||
{ShellGeometryPointKind::stiffness, 3U, {g, -g, g}},
|
||||
{ShellGeometryPointKind::stiffness, 4U, {g, g, -g}},
|
||||
{ShellGeometryPointKind::stiffness, 5U, {g, g, g}},
|
||||
{ShellGeometryPointKind::stiffness, 6U, {-g, g, -g}},
|
||||
{ShellGeometryPointKind::stiffness, 7U, {-g, g, g}},
|
||||
{ShellGeometryPointKind::tying, 0U, {0.0, -1.0, 0.0}},
|
||||
{ShellGeometryPointKind::tying, 1U, {0.0, 1.0, 0.0}},
|
||||
{ShellGeometryPointKind::tying, 2U, {-1.0, 0.0, 0.0}},
|
||||
{ShellGeometryPointKind::tying, 3U, {1.0, 0.0, 0.0}},
|
||||
{ShellGeometryPointKind::recovery, 0U, {-g, -g, 0.0}},
|
||||
{ShellGeometryPointKind::recovery, 1U, {g, -g, 0.0}},
|
||||
{ShellGeometryPointKind::recovery, 2U, {g, g, 0.0}},
|
||||
{ShellGeometryPointKind::recovery, 3U, {-g, g, 0.0}}};
|
||||
}();
|
||||
return points;
|
||||
ShellGeometryValidationPoints() noexcept {
|
||||
static const std::array<ShellGeometryValidationPoint, 17> points = [] {
|
||||
const double g = 1.0 / std::sqrt(3.0);
|
||||
return std::array<ShellGeometryValidationPoint, 17>{
|
||||
ShellGeometryValidationPoint{
|
||||
ShellGeometryPointKind::kCenter, 0U, {0.0, 0.0, 0.0}},
|
||||
{ShellGeometryPointKind::kStiffness, 0U, {-g, -g, -g}},
|
||||
{ShellGeometryPointKind::kStiffness, 1U, {-g, -g, g}},
|
||||
{ShellGeometryPointKind::kStiffness, 2U, {g, -g, -g}},
|
||||
{ShellGeometryPointKind::kStiffness, 3U, {g, -g, g}},
|
||||
{ShellGeometryPointKind::kStiffness, 4U, {g, g, -g}},
|
||||
{ShellGeometryPointKind::kStiffness, 5U, {g, g, g}},
|
||||
{ShellGeometryPointKind::kStiffness, 6U, {-g, g, -g}},
|
||||
{ShellGeometryPointKind::kStiffness, 7U, {-g, g, g}},
|
||||
{ShellGeometryPointKind::kTying, 0U, {0.0, -1.0, 0.0}},
|
||||
{ShellGeometryPointKind::kTying, 1U, {0.0, 1.0, 0.0}},
|
||||
{ShellGeometryPointKind::kTying, 2U, {-1.0, 0.0, 0.0}},
|
||||
{ShellGeometryPointKind::kTying, 3U, {1.0, 0.0, 0.0}},
|
||||
{ShellGeometryPointKind::kRecovery, 0U, {-g, -g, 0.0}},
|
||||
{ShellGeometryPointKind::kRecovery, 1U, {g, -g, 0.0}},
|
||||
{ShellGeometryPointKind::kRecovery, 2U, {g, g, 0.0}},
|
||||
{ShellGeometryPointKind::kRecovery, 3U, {-g, g, 0.0}}};
|
||||
}();
|
||||
return points;
|
||||
}
|
||||
|
||||
Result<ShellGeometry> preprocessShellGeometry(
|
||||
Result<ShellGeometry> PreprocessShellGeometry(
|
||||
const std::vector<Node>& nodes,
|
||||
const std::vector<Mitc4ShellDefinition>& elements,
|
||||
const std::vector<ShellSection>& sections) {
|
||||
ShellGeometry geometry;
|
||||
geometry.elementData.reserve(elements.size());
|
||||
std::vector<ElementWork> work;
|
||||
work.reserve(elements.size());
|
||||
ShellGeometry geometry;
|
||||
geometry.element_data.reserve(elements.size());
|
||||
std::vector<ElementWork> work;
|
||||
work.reserve(elements.size());
|
||||
|
||||
const double g = 1.0 / std::sqrt(3.0);
|
||||
const std::array<Vector3, 4> surfaceGaussPoints{
|
||||
Vector3{-g, -g, 0.0},
|
||||
Vector3{g, -g, 0.0},
|
||||
Vector3{g, g, 0.0},
|
||||
Vector3{-g, g, 0.0}};
|
||||
const double g = 1.0 / std::sqrt(3.0);
|
||||
const std::array<Vector3, 4> surface_gauss_points{
|
||||
Vector3{-g, -g, 0.0}, Vector3{g, -g, 0.0}, Vector3{g, g, 0.0},
|
||||
Vector3{-g, g, 0.0}};
|
||||
|
||||
for (std::size_t elementIndex = 0U;
|
||||
elementIndex < elements.size();
|
||||
++elementIndex) {
|
||||
const auto& element = elements[elementIndex];
|
||||
ElementWork current{};
|
||||
for (std::size_t localNode = 0U; localNode < 4U; ++localNode) {
|
||||
if (element.nodeIndices[localNode] >= nodes.size()) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell geometry references an unavailable internal node.");
|
||||
}
|
||||
current.coordinates[localNode] =
|
||||
nodes[element.nodeIndices[localNode]].coordinates;
|
||||
if (!isFinite(current.coordinates[localNode])) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell geometry contains a nonfinite source coordinate.");
|
||||
}
|
||||
for (std::size_t element_index = 0U; element_index < elements.size();
|
||||
++element_index) {
|
||||
const auto& element = elements[element_index];
|
||||
ElementWork current{};
|
||||
for (std::size_t local_node = 0U; local_node < 4U; ++local_node) {
|
||||
if (element.node_indices[local_node] >= nodes.size()) {
|
||||
return GeometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.source_id.source_label_text,
|
||||
"Shell geometry references an unavailable internal node.");
|
||||
}
|
||||
current.coordinates[local_node] =
|
||||
nodes[element.node_indices[local_node]].coordinates;
|
||||
if (!IsFinite(current.coordinates[local_node])) {
|
||||
return GeometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.source_id.source_label_text,
|
||||
"Shell geometry contains a nonfinite source coordinate.");
|
||||
}
|
||||
}
|
||||
for (std::size_t first = 0U; first < 4U; ++first) {
|
||||
for (std::size_t second = first + 1U; second < 4U; ++second) {
|
||||
if (element.node_indices[first] == element.node_indices[second] ||
|
||||
SameCoordinates(current.coordinates[first],
|
||||
current.coordinates[second])) {
|
||||
return GeometryFailure("invalid-shell-geometry", element.location,
|
||||
"ELEMENT", element.source_id.source_label_text,
|
||||
"Shell geometry contains duplicate nodes.");
|
||||
}
|
||||
for (std::size_t first = 0U; first < 4U; ++first) {
|
||||
for (std::size_t second = first + 1U; second < 4U; ++second) {
|
||||
if (element.nodeIndices[first] == element.nodeIndices[second] ||
|
||||
sameCoordinates(
|
||||
current.coordinates[first], current.coordinates[second])) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell geometry contains duplicate nodes.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ShapeData center = shapeData(0.0, 0.0);
|
||||
const Vector3 centerXi =
|
||||
derivativeSum(center.xiDerivatives, current.coordinates);
|
||||
const Vector3 centerEta =
|
||||
derivativeSum(center.etaDerivatives, current.coordinates);
|
||||
const Vector3 centerCross = cross(centerXi, centerEta);
|
||||
const double centerMeasure = norm(centerCross);
|
||||
if (!isFinite(centerXi) || !isFinite(centerEta) ||
|
||||
!isFinite(centerCross) || !std::isfinite(centerMeasure) ||
|
||||
!(centerMeasure > 0.0)) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell center has no finite nonzero normal candidate.");
|
||||
}
|
||||
current.normal = scale(1.0 / centerMeasure, centerCross);
|
||||
|
||||
if (segmentsProperlyIntersect(
|
||||
current.coordinates[0], current.coordinates[1],
|
||||
current.coordinates[2], current.coordinates[3], current.normal) ||
|
||||
segmentsProperlyIntersect(
|
||||
current.coordinates[1], current.coordinates[2],
|
||||
current.coordinates[3], current.coordinates[0], current.normal)) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell boundary is self-intersecting in the center-normal projection.");
|
||||
}
|
||||
|
||||
double areaWeight = 0.0;
|
||||
for (const auto& point : surfaceGaussPoints) {
|
||||
const ShapeData shape = shapeData(point[0], point[1]);
|
||||
const Vector3 tangentXi =
|
||||
derivativeSum(shape.xiDerivatives, current.coordinates);
|
||||
const Vector3 tangentEta =
|
||||
derivativeSum(shape.etaDerivatives, current.coordinates);
|
||||
const Vector3 areaVector = cross(tangentXi, tangentEta);
|
||||
const double measure = norm(areaVector);
|
||||
if (!isFinite(tangentXi) || !isFinite(tangentEta) ||
|
||||
!isFinite(areaVector) || !std::isfinite(measure) ||
|
||||
!(measure > 0.0) || !(dot(areaVector, current.normal) > 0.0)) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell surface is zero-area or locally reversed at a required point.");
|
||||
}
|
||||
areaWeight += measure;
|
||||
}
|
||||
if (!std::isfinite(areaWeight) || !(areaWeight > 0.0)) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell surface-area weight is nonfinite or zero.");
|
||||
}
|
||||
current.areaWeight = areaWeight;
|
||||
work.push_back(current);
|
||||
geometry.elementData.push_back({
|
||||
static_cast<EntityIndex>(elementIndex), current.normal, areaWeight});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::size_t>> incident(nodes.size());
|
||||
for (std::size_t elementIndex = 0U;
|
||||
elementIndex < elements.size();
|
||||
++elementIndex) {
|
||||
for (const EntityIndex nodeIndex : elements[elementIndex].nodeIndices) {
|
||||
incident[nodeIndex].push_back(elementIndex);
|
||||
}
|
||||
const ShapeData center = ShapeDataAt(0.0, 0.0);
|
||||
const Vector3 center_xi =
|
||||
DerivativeSum(center.xi_derivatives, current.coordinates);
|
||||
const Vector3 center_eta =
|
||||
DerivativeSum(center.eta_derivatives, current.coordinates);
|
||||
const Vector3 center_cross = Cross(center_xi, center_eta);
|
||||
const double center_measure = Norm(center_cross);
|
||||
if (!IsFinite(center_xi) || !IsFinite(center_eta) ||
|
||||
!IsFinite(center_cross) || !std::isfinite(center_measure) ||
|
||||
!(center_measure > 0.0)) {
|
||||
return GeometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.source_id.source_label_text,
|
||||
"Shell center has no finite nonzero normal candidate.");
|
||||
}
|
||||
current.normal = Scale(1.0 / center_measure, center_cross);
|
||||
|
||||
if (SegmentsProperlyIntersect(
|
||||
current.coordinates[0], current.coordinates[1],
|
||||
current.coordinates[2], current.coordinates[3], current.normal) ||
|
||||
SegmentsProperlyIntersect(
|
||||
current.coordinates[1], current.coordinates[2],
|
||||
current.coordinates[3], current.coordinates[0], current.normal)) {
|
||||
return GeometryFailure("invalid-shell-geometry", element.location,
|
||||
"ELEMENT", element.source_id.source_label_text,
|
||||
"Shell boundary is self-intersecting in the "
|
||||
"center-normal projection.");
|
||||
}
|
||||
|
||||
geometry.nodalFrames.reserve(nodes.size());
|
||||
std::vector<const ShellNodeInitialFrame*> frameByNode(nodes.size(), nullptr);
|
||||
for (std::size_t nodeIndex = 0U; nodeIndex < incident.size(); ++nodeIndex) {
|
||||
auto& nodeIncident = incident[nodeIndex];
|
||||
if (nodeIncident.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::sort(
|
||||
nodeIncident.begin(), nodeIncident.end(),
|
||||
[&elements](std::size_t left, std::size_t right) {
|
||||
return sourceIdentityLess(
|
||||
elements[left], left, elements[right], right);
|
||||
});
|
||||
for (std::size_t first = 0U; first < nodeIncident.size(); ++first) {
|
||||
for (std::size_t second = first + 1U;
|
||||
second < nodeIncident.size();
|
||||
++second) {
|
||||
const double pairDot = dot(
|
||||
work[nodeIncident[first]].normal,
|
||||
work[nodeIncident[second]].normal);
|
||||
if (!std::isfinite(pairDot) || !(pairDot > 0.0)) {
|
||||
return geometryFailure(
|
||||
"opposed-incident-normal", nodes[nodeIndex].location,
|
||||
"NODE", nodes[nodeIndex].sourceId.source_label_text,
|
||||
"Incident shell normal candidates do not share a positive orientation hemisphere.");
|
||||
}
|
||||
}
|
||||
}
|
||||
double area_weight = 0.0;
|
||||
for (const auto& point : surface_gauss_points) {
|
||||
const ShapeData shape = ShapeDataAt(point[0], point[1]);
|
||||
const Vector3 tangent_xi =
|
||||
DerivativeSum(shape.xi_derivatives, current.coordinates);
|
||||
const Vector3 tangent_eta =
|
||||
DerivativeSum(shape.eta_derivatives, current.coordinates);
|
||||
const Vector3 area_vector = Cross(tangent_xi, tangent_eta);
|
||||
const double measure = Norm(area_vector);
|
||||
if (!IsFinite(tangent_xi) || !IsFinite(tangent_eta) ||
|
||||
!IsFinite(area_vector) || !std::isfinite(measure) ||
|
||||
!(measure > 0.0) || !(Dot(area_vector, current.normal) > 0.0)) {
|
||||
return GeometryFailure("invalid-shell-geometry", element.location,
|
||||
"ELEMENT", element.source_id.source_label_text,
|
||||
"Shell surface is zero-area or locally reversed "
|
||||
"at a required point.");
|
||||
}
|
||||
area_weight += measure;
|
||||
}
|
||||
if (!std::isfinite(area_weight) || !(area_weight > 0.0)) {
|
||||
return GeometryFailure("invalid-shell-geometry", element.location,
|
||||
"ELEMENT", element.source_id.source_label_text,
|
||||
"Shell surface-area weight is nonfinite or zero.");
|
||||
}
|
||||
current.area_weight = area_weight;
|
||||
work.push_back(current);
|
||||
geometry.element_data.push_back(
|
||||
{static_cast<EntityIndex>(element_index), current.normal, area_weight});
|
||||
}
|
||||
|
||||
double maximumWeight = 0.0;
|
||||
for (const std::size_t elementIndex : nodeIncident) {
|
||||
maximumWeight = std::max(maximumWeight, work[elementIndex].areaWeight);
|
||||
}
|
||||
Vector3 directorSum{};
|
||||
for (const std::size_t elementIndex : nodeIncident) {
|
||||
directorSum = add(
|
||||
directorSum,
|
||||
scale(
|
||||
work[elementIndex].areaWeight / maximumWeight,
|
||||
work[elementIndex].normal));
|
||||
}
|
||||
const double directorNorm = norm(directorSum);
|
||||
if (!isFinite(directorSum) || !std::isfinite(directorNorm) ||
|
||||
!(directorNorm > 0.0)) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
|
||||
nodes[nodeIndex].sourceId.source_label_text,
|
||||
"Area-weighted shell director is nonfinite or zero.");
|
||||
}
|
||||
const Vector3 director = scale(1.0 / directorNorm, directorSum);
|
||||
std::vector<std::vector<std::size_t>> incident(nodes.size());
|
||||
for (std::size_t element_index = 0U; element_index < elements.size();
|
||||
++element_index) {
|
||||
for (const EntityIndex node_index : elements[element_index].node_indices) {
|
||||
incident[node_index].push_back(element_index);
|
||||
}
|
||||
}
|
||||
|
||||
const std::array<Vector3, 3> globalAxes{
|
||||
Vector3{1.0, 0.0, 0.0},
|
||||
Vector3{0.0, 1.0, 0.0},
|
||||
Vector3{0.0, 0.0, 1.0}};
|
||||
std::size_t selectedAxis = 0U;
|
||||
double selectedAlignment = std::abs(dot(globalAxes[0], director));
|
||||
for (std::size_t axis = 1U; axis < globalAxes.size(); ++axis) {
|
||||
const double alignment = std::abs(dot(globalAxes[axis], director));
|
||||
if (alignment < selectedAlignment) {
|
||||
selectedAlignment = alignment;
|
||||
selectedAxis = axis;
|
||||
}
|
||||
geometry.nodal_frames.reserve(nodes.size());
|
||||
std::vector<const ShellNodeInitialFrame*> frame_by_node(nodes.size(),
|
||||
nullptr);
|
||||
for (std::size_t node_index = 0U; node_index < incident.size();
|
||||
++node_index) {
|
||||
auto& node_incident = incident[node_index];
|
||||
if (node_incident.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::sort(node_incident.begin(), node_incident.end(),
|
||||
[&elements](std::size_t left, std::size_t right) {
|
||||
return SourceIdentityLess(elements[left], left, elements[right],
|
||||
right);
|
||||
});
|
||||
for (std::size_t first = 0U; first < node_incident.size(); ++first) {
|
||||
for (std::size_t second = first + 1U; second < node_incident.size();
|
||||
++second) {
|
||||
const double pair_dot = Dot(work[node_incident[first]].normal,
|
||||
work[node_incident[second]].normal);
|
||||
if (!std::isfinite(pair_dot) || !(pair_dot > 0.0)) {
|
||||
return GeometryFailure("opposed-incident-normal",
|
||||
nodes[node_index].location, "NODE",
|
||||
nodes[node_index].source_id.source_label_text,
|
||||
"Incident shell normal candidates do not "
|
||||
"share a positive orientation hemisphere.");
|
||||
}
|
||||
const Vector3 tangentCandidate = subtract(
|
||||
globalAxes[selectedAxis],
|
||||
scale(dot(globalAxes[selectedAxis], director), director));
|
||||
const double tangentNorm = norm(tangentCandidate);
|
||||
if (!isFinite(tangentCandidate) || !std::isfinite(tangentNorm) ||
|
||||
!(tangentNorm > 0.0)) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
|
||||
nodes[nodeIndex].sourceId.source_label_text,
|
||||
"Least-aligned-axis tangent frame construction failed.");
|
||||
}
|
||||
const Vector3 tangentA = scale(1.0 / tangentNorm, tangentCandidate);
|
||||
const Vector3 tangentB = cross(director, tangentA);
|
||||
if (!isFinite(tangentB) || !(norm(tangentB) > 0.0)) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
|
||||
nodes[nodeIndex].sourceId.source_label_text,
|
||||
"Right-handed shell tangent frame construction failed.");
|
||||
}
|
||||
geometry.nodalFrames.push_back({
|
||||
static_cast<EntityIndex>(nodeIndex), director, tangentA, tangentB});
|
||||
}
|
||||
}
|
||||
|
||||
// Build lookup only after frame storage is complete so later code never
|
||||
// observes a pointer invalidated by vector growth.
|
||||
for (const auto& frame : geometry.nodalFrames) {
|
||||
frameByNode[frame.nodeIndex] = &frame;
|
||||
double maximum_weight = 0.0;
|
||||
for (const std::size_t element_index : node_incident) {
|
||||
maximum_weight =
|
||||
std::max(maximum_weight, work[element_index].area_weight);
|
||||
}
|
||||
Vector3 director_sum{};
|
||||
for (const std::size_t element_index : node_incident) {
|
||||
director_sum = Add(director_sum,
|
||||
Scale(work[element_index].area_weight / maximum_weight,
|
||||
work[element_index].normal));
|
||||
}
|
||||
const double director_norm = Norm(director_sum);
|
||||
if (!IsFinite(director_sum) || !std::isfinite(director_norm) ||
|
||||
!(director_norm > 0.0)) {
|
||||
return GeometryFailure(
|
||||
"invalid-shell-director", nodes[node_index].location, "NODE",
|
||||
nodes[node_index].source_id.source_label_text,
|
||||
"Area-weighted shell director is nonfinite or zero.");
|
||||
}
|
||||
const Vector3 director = Scale(1.0 / director_norm, director_sum);
|
||||
|
||||
const std::array<Vector3, 3> global_axes{
|
||||
Vector3{1.0, 0.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{0.0, 0.0, 1.0}};
|
||||
std::size_t selected_axis = 0U;
|
||||
double selected_alignment = std::abs(Dot(global_axes[0], director));
|
||||
for (std::size_t axis = 1U; axis < global_axes.size(); ++axis) {
|
||||
const double alignment = std::abs(Dot(global_axes[axis], director));
|
||||
if (alignment < selected_alignment) {
|
||||
selected_alignment = alignment;
|
||||
selected_axis = axis;
|
||||
}
|
||||
}
|
||||
const Vector3 tangent_candidate =
|
||||
Subtract(global_axes[selected_axis],
|
||||
Scale(Dot(global_axes[selected_axis], director), director));
|
||||
const double tangent_norm = Norm(tangent_candidate);
|
||||
if (!IsFinite(tangent_candidate) || !std::isfinite(tangent_norm) ||
|
||||
!(tangent_norm > 0.0)) {
|
||||
return GeometryFailure(
|
||||
"invalid-shell-director", nodes[node_index].location, "NODE",
|
||||
nodes[node_index].source_id.source_label_text,
|
||||
"Least-aligned-axis tangent frame construction failed.");
|
||||
}
|
||||
const Vector3 tangent_a = Scale(1.0 / tangent_norm, tangent_candidate);
|
||||
const Vector3 tangent_b = Cross(director, tangent_a);
|
||||
if (!IsFinite(tangent_b) || !(Norm(tangent_b) > 0.0)) {
|
||||
return GeometryFailure(
|
||||
"invalid-shell-director", nodes[node_index].location, "NODE",
|
||||
nodes[node_index].source_id.source_label_text,
|
||||
"Right-handed shell tangent frame construction failed.");
|
||||
}
|
||||
geometry.nodal_frames.push_back(
|
||||
{static_cast<EntityIndex>(node_index), director, tangent_a, tangent_b});
|
||||
}
|
||||
|
||||
// Build lookup only after frame storage is complete so later code never
|
||||
// observes a pointer invalidated by vector growth.
|
||||
for (const auto& frame : geometry.nodal_frames) {
|
||||
frame_by_node[frame.node_index] = &frame;
|
||||
}
|
||||
|
||||
for (std::size_t element_index = 0U; element_index < elements.size();
|
||||
++element_index) {
|
||||
const auto& element = elements[element_index];
|
||||
if (element.section_index >= sections.size()) {
|
||||
return GeometryFailure("invalid-shell-jacobian", element.location,
|
||||
"ELEMENT", element.source_id.source_label_text,
|
||||
"Shell geometry cannot resolve its thickness for "
|
||||
"Jacobian validation.");
|
||||
}
|
||||
const double thickness = sections[element.section_index].thickness;
|
||||
std::array<Vector3, 4> directors{};
|
||||
for (std::size_t local_node = 0U; local_node < 4U; ++local_node) {
|
||||
const auto* frame = frame_by_node[element.node_indices[local_node]];
|
||||
if (frame == nullptr) {
|
||||
return GeometryFailure("invalid-shell-director", element.location,
|
||||
"ELEMENT", element.source_id.source_label_text,
|
||||
"Shell element is missing a nodal director.");
|
||||
}
|
||||
directors[local_node] = frame->director;
|
||||
}
|
||||
|
||||
for (std::size_t elementIndex = 0U;
|
||||
elementIndex < elements.size();
|
||||
++elementIndex) {
|
||||
const auto& element = elements[elementIndex];
|
||||
if (element.sectionIndex >= sections.size()) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-jacobian", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell geometry cannot resolve its thickness for Jacobian validation.");
|
||||
}
|
||||
const double thickness = sections[element.sectionIndex].thickness;
|
||||
std::array<Vector3, 4> directors{};
|
||||
for (std::size_t localNode = 0U; localNode < 4U; ++localNode) {
|
||||
const auto* frame = frameByNode[element.nodeIndices[localNode]];
|
||||
if (frame == nullptr) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-director", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell element is missing a nodal director.");
|
||||
}
|
||||
directors[localNode] = frame->director;
|
||||
}
|
||||
for (const auto& point : ShellGeometryValidationPoints()) {
|
||||
const double xi = point.natural_coordinates[0];
|
||||
const double eta = point.natural_coordinates[1];
|
||||
const double zeta = point.natural_coordinates[2];
|
||||
const ShapeData shape = ShapeDataAt(xi, eta);
|
||||
const Vector3 midsurface_xi =
|
||||
DerivativeSum(shape.xi_derivatives, work[element_index].coordinates);
|
||||
const Vector3 midsurface_eta =
|
||||
DerivativeSum(shape.eta_derivatives, work[element_index].coordinates);
|
||||
const Vector3 area_vector = Cross(midsurface_xi, midsurface_eta);
|
||||
const double surface_measure = Norm(area_vector);
|
||||
if (!IsFinite(midsurface_xi) || !IsFinite(midsurface_eta) ||
|
||||
!IsFinite(area_vector) || !std::isfinite(surface_measure) ||
|
||||
!(surface_measure > 0.0) ||
|
||||
!(Dot(area_vector, work[element_index].normal) > 0.0)) {
|
||||
return GeometryFailure("invalid-shell-geometry", element.location,
|
||||
"ELEMENT", element.source_id.source_label_text,
|
||||
"Shell surface basis is nonfinite, zero, or "
|
||||
"reversed at a required point.");
|
||||
}
|
||||
|
||||
for (const auto& point : shellGeometryValidationPoints()) {
|
||||
const double xi = point.naturalCoordinates[0];
|
||||
const double eta = point.naturalCoordinates[1];
|
||||
const double zeta = point.naturalCoordinates[2];
|
||||
const ShapeData shape = shapeData(xi, eta);
|
||||
const Vector3 midsurfaceXi =
|
||||
derivativeSum(shape.xiDerivatives, work[elementIndex].coordinates);
|
||||
const Vector3 midsurfaceEta =
|
||||
derivativeSum(shape.etaDerivatives, work[elementIndex].coordinates);
|
||||
const Vector3 areaVector = cross(midsurfaceXi, midsurfaceEta);
|
||||
const double surfaceMeasure = norm(areaVector);
|
||||
if (!isFinite(midsurfaceXi) || !isFinite(midsurfaceEta) ||
|
||||
!isFinite(areaVector) || !std::isfinite(surfaceMeasure) ||
|
||||
!(surfaceMeasure > 0.0) ||
|
||||
!(dot(areaVector, work[elementIndex].normal) > 0.0)) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-geometry", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell surface basis is nonfinite, zero, or reversed at a required point.");
|
||||
}
|
||||
|
||||
const Vector3 directorXi =
|
||||
weightedSum(shape.xiDerivatives, directors);
|
||||
const Vector3 directorEta =
|
||||
weightedSum(shape.etaDerivatives, directors);
|
||||
const Vector3 directorValue = weightedSum(shape.values, directors);
|
||||
const Vector3 covariantXi = add(
|
||||
midsurfaceXi, scale(0.5 * thickness * zeta, directorXi));
|
||||
const Vector3 covariantEta = add(
|
||||
midsurfaceEta, scale(0.5 * thickness * zeta, directorEta));
|
||||
const Vector3 covariantZeta = scale(0.5 * thickness, directorValue);
|
||||
const double jacobian =
|
||||
dot(covariantXi, cross(covariantEta, covariantZeta));
|
||||
if (!isFinite(covariantXi) || !isFinite(covariantEta) ||
|
||||
!isFinite(covariantZeta) || !std::isfinite(jacobian) ||
|
||||
!(jacobian > 0.0)) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-jacobian", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell Jacobian is nonfinite or nonpositive at a required point.");
|
||||
}
|
||||
const Vector3 reciprocalXi =
|
||||
scale(1.0 / jacobian, cross(covariantEta, covariantZeta));
|
||||
const Vector3 reciprocalEta =
|
||||
scale(1.0 / jacobian, cross(covariantZeta, covariantXi));
|
||||
const Vector3 reciprocalZeta =
|
||||
scale(1.0 / jacobian, cross(covariantXi, covariantEta));
|
||||
if (!isFinite(reciprocalXi) || !isFinite(reciprocalEta) ||
|
||||
!isFinite(reciprocalZeta)) {
|
||||
return geometryFailure(
|
||||
"invalid-shell-jacobian", element.location, "ELEMENT",
|
||||
element.sourceId.source_label_text,
|
||||
"Shell reciprocal basis is nonfinite at a required point.");
|
||||
}
|
||||
}
|
||||
const Vector3 director_xi = WeightedSum(shape.xi_derivatives, directors);
|
||||
const Vector3 director_eta =
|
||||
WeightedSum(shape.eta_derivatives, directors);
|
||||
const Vector3 director_value = WeightedSum(shape.values, directors);
|
||||
const Vector3 covariant_xi =
|
||||
Add(midsurface_xi, Scale(0.5 * thickness * zeta, director_xi));
|
||||
const Vector3 covariant_eta =
|
||||
Add(midsurface_eta, Scale(0.5 * thickness * zeta, director_eta));
|
||||
const Vector3 covariant_zeta = Scale(0.5 * thickness, director_value);
|
||||
const double jacobian =
|
||||
Dot(covariant_xi, Cross(covariant_eta, covariant_zeta));
|
||||
if (!IsFinite(covariant_xi) || !IsFinite(covariant_eta) ||
|
||||
!IsFinite(covariant_zeta) || !std::isfinite(jacobian) ||
|
||||
!(jacobian > 0.0)) {
|
||||
return GeometryFailure(
|
||||
"invalid-shell-jacobian", element.location, "ELEMENT",
|
||||
element.source_id.source_label_text,
|
||||
"Shell Jacobian is nonfinite or nonpositive at a required point.");
|
||||
}
|
||||
const Vector3 reciprocal_xi =
|
||||
Scale(1.0 / jacobian, Cross(covariant_eta, covariant_zeta));
|
||||
const Vector3 reciprocal_eta =
|
||||
Scale(1.0 / jacobian, Cross(covariant_zeta, covariant_xi));
|
||||
const Vector3 reciprocal_zeta =
|
||||
Scale(1.0 / jacobian, Cross(covariant_xi, covariant_eta));
|
||||
if (!IsFinite(reciprocal_xi) || !IsFinite(reciprocal_eta) ||
|
||||
!IsFinite(reciprocal_zeta)) {
|
||||
return GeometryFailure(
|
||||
"invalid-shell-jacobian", element.location, "ELEMENT",
|
||||
element.source_id.source_label_text,
|
||||
"Shell reciprocal basis is nonfinite at a required point.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result<ShellGeometry>::Success(std::move(geometry));
|
||||
return Result<ShellGeometry>::Success(std::move(geometry));
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
} // namespace fesa
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "fesa/results/result_recovery.hpp"
|
||||
|
||||
#include "fesa/elements/euler_beam_3d.hpp"
|
||||
#include "fesa/elements/mitc4_shell.hpp"
|
||||
#include "fesa/elements/euler_beam_3d.h"
|
||||
#include "fesa/elements/mitc4_shell.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -131,15 +131,15 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
const SparseMatrix& fullStiffness,
|
||||
const AnalysisState& state) {
|
||||
const Domain& domain = model.domain();
|
||||
if (domain.nodes().size() >
|
||||
if (domain.Nodes().size() >
|
||||
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-dimensions",
|
||||
{domain.sourcePath(), 0U},
|
||||
domain.sourceContentIdentity(),
|
||||
{domain.SourcePath(), 0U},
|
||||
domain.SourceContentIdentity(),
|
||||
"The semantic node count cannot be represented in full-DOF space.");
|
||||
}
|
||||
const std::size_t fullCount = domain.nodes().size() * kDofsPerNode;
|
||||
const std::size_t fullCount = domain.Nodes().size() * kDofsPerNode;
|
||||
if (dofs.fullDofCount() != fullCount ||
|
||||
fullStiffness.Rows() != fullCount ||
|
||||
fullStiffness.Columns() != fullCount ||
|
||||
@@ -150,8 +150,8 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
state.reaction().Size() != fullCount) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-dimensions",
|
||||
{domain.sourcePath(), 0U},
|
||||
domain.sourceContentIdentity(),
|
||||
{domain.SourcePath(), 0U},
|
||||
domain.SourceContentIdentity(),
|
||||
"Model, DOF, stiffness, and AnalysisState full-space dimensions must agree.");
|
||||
}
|
||||
const Status matrixStatus = fullStiffness.Validate();
|
||||
@@ -161,8 +161,8 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
if (!finite(state.displacement()) || !finite(state.externalForce())) {
|
||||
return recoveryFailure(
|
||||
"nonfinite-recovery-value",
|
||||
{domain.sourcePath(), 0U},
|
||||
domain.sourceContentIdentity(),
|
||||
{domain.SourcePath(), 0U},
|
||||
domain.SourceContentIdentity(),
|
||||
"Displacement and external-force inputs must be finite.");
|
||||
}
|
||||
|
||||
@@ -176,8 +176,8 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
!strictlyIncreasing(constrainedDofs)) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-order",
|
||||
{domain.sourcePath(), 0U},
|
||||
domain.sourceContentIdentity(),
|
||||
{domain.SourcePath(), 0U},
|
||||
domain.SourceContentIdentity(),
|
||||
"Free and constrained DOFs must form stable increasing full-space orders.");
|
||||
}
|
||||
std::vector<unsigned char> ownership(fullCount, 0U);
|
||||
@@ -188,7 +188,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
dofs.freeEquation(fullDof) != equation) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-order",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(fullDof),
|
||||
"Free equations must match stable full-DOF order.");
|
||||
}
|
||||
@@ -202,7 +202,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
dofs.freeEquation(fullDof).has_value()) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-order",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(fullDof),
|
||||
"Constrained DOFs must be unique and absent from free equations.");
|
||||
}
|
||||
@@ -211,7 +211,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
dofs.prescribedValues()[constrained]) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-state",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(fullDof),
|
||||
"Constrained displacement must equal its prescribed value before recovery.");
|
||||
}
|
||||
@@ -220,40 +220,40 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
} catch (const std::out_of_range&) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-dimensions",
|
||||
{domain.sourcePath(), 0U},
|
||||
domain.sourceContentIdentity(),
|
||||
{domain.SourcePath(), 0U},
|
||||
domain.SourceContentIdentity(),
|
||||
"DofManager equation storage must cover every full DOF.");
|
||||
}
|
||||
if (std::find(ownership.begin(), ownership.end(), 0U) != ownership.end()) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-order",
|
||||
{domain.sourcePath(), 0U},
|
||||
domain.sourceContentIdentity(),
|
||||
{domain.SourcePath(), 0U},
|
||||
domain.SourceContentIdentity(),
|
||||
"Free and constrained DOFs must partition the full range.");
|
||||
}
|
||||
|
||||
EntityIndex previousElement = 0U;
|
||||
bool firstElement = true;
|
||||
for (const EntityIndex element : model.activeElements()) {
|
||||
if (element >= domain.elements().size() ||
|
||||
if (element >= domain.Elements().size() ||
|
||||
(!firstElement && element <= previousElement)) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-entity",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(element),
|
||||
"Active elements must be unique in stable internal-index order.");
|
||||
}
|
||||
firstElement = false;
|
||||
previousElement = element;
|
||||
const auto& definition = domain.elements()[element];
|
||||
if (definition.nodeIndices[0U] >= domain.nodes().size() ||
|
||||
definition.nodeIndices[1U] >= domain.nodes().size() ||
|
||||
definition.materialIndex >= domain.materials().size() ||
|
||||
definition.sectionIndex >= domain.sections().size()) {
|
||||
const auto& definition = domain.Elements()[element];
|
||||
if (definition.node_indices[0U] >= domain.Nodes().size() ||
|
||||
definition.node_indices[1U] >= domain.Nodes().size() ||
|
||||
definition.material_index >= domain.Materials().size() ||
|
||||
definition.section_index >= domain.Sections().size()) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-entity",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Active beam references must resolve before recovery.");
|
||||
}
|
||||
try {
|
||||
@@ -263,7 +263,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
component < kDofsPerNode;
|
||||
++component) {
|
||||
const std::size_t expected =
|
||||
static_cast<std::size_t>(definition.nodeIndices[endpoint]) *
|
||||
static_cast<std::size_t>(definition.node_indices[endpoint]) *
|
||||
kDofsPerNode +
|
||||
component;
|
||||
if (scatter[endpoint * kDofsPerNode + component] != expected ||
|
||||
@@ -271,7 +271,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-order",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Element scatter must preserve endpoint/component full-DOF order.");
|
||||
}
|
||||
}
|
||||
@@ -280,51 +280,51 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-entity",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Every active element requires one twelve-DOF scatter map.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!model.activeElements().empty() && !domain.shellElements().empty()) {
|
||||
if (!model.activeElements().empty() && !domain.ShellElements().empty()) {
|
||||
return recoveryFailure(
|
||||
"unsupported-mixed-element-model",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
"B33:FESA-MITC4",
|
||||
"Result recovery does not support mixed beam and shell models.");
|
||||
}
|
||||
if (domain.shellElements().size() >
|
||||
if (domain.ShellElements().size() >
|
||||
static_cast<std::size_t>(
|
||||
(std::numeric_limits<EntityIndex>::max)())) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-dimensions",
|
||||
{domain.sourcePath(), 0U},
|
||||
domain.sourceContentIdentity(),
|
||||
{domain.SourcePath(), 0U},
|
||||
domain.SourceContentIdentity(),
|
||||
"The shell element count cannot be represented by stable element identities.");
|
||||
}
|
||||
for (std::size_t elementOrder = 0U;
|
||||
elementOrder < domain.shellElements().size();
|
||||
elementOrder < domain.ShellElements().size();
|
||||
++elementOrder) {
|
||||
const auto& definition = domain.shellElements()[elementOrder];
|
||||
if (definition.materialIndex >= domain.materials().size() ||
|
||||
definition.sectionIndex >= domain.shellSections().size()) {
|
||||
const auto& definition = domain.ShellElements()[elementOrder];
|
||||
if (definition.material_index >= domain.Materials().size() ||
|
||||
definition.section_index >= domain.ShellSections().size()) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-entity",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Active shell material and section references must resolve before recovery.");
|
||||
}
|
||||
try {
|
||||
const auto& scatter = dofs.shellElementScatter(
|
||||
static_cast<EntityIndex>(elementOrder));
|
||||
for (std::size_t nodePosition = 0U;
|
||||
nodePosition < definition.nodeIndices.size();
|
||||
nodePosition < definition.node_indices.size();
|
||||
++nodePosition) {
|
||||
const EntityIndex node = definition.nodeIndices[nodePosition];
|
||||
if (node >= domain.nodes().size()) {
|
||||
const EntityIndex node = definition.node_indices[nodePosition];
|
||||
if (node >= domain.Nodes().size()) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-entity",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Active shell node references must resolve before recovery.");
|
||||
}
|
||||
for (std::size_t component = 0U;
|
||||
@@ -339,7 +339,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-order",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Shell scatter must preserve node/component full-DOF order.");
|
||||
}
|
||||
}
|
||||
@@ -348,7 +348,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-entity",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Every active shell requires one twenty-four-DOF scatter map.");
|
||||
}
|
||||
}
|
||||
@@ -381,7 +381,7 @@ bool tryPositiveInteger(const std::string& text, std::int64_t& value) {
|
||||
Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
|
||||
const NodalLoad& load) {
|
||||
std::vector<const NodeSet*> sets;
|
||||
for (const auto& set : domain.nodeSets()) {
|
||||
for (const auto& set : domain.NodeSets()) {
|
||||
if (equalName(set.name, load.target)) {
|
||||
sets.push_back(&set);
|
||||
}
|
||||
@@ -389,8 +389,8 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
|
||||
std::vector<EntityIndex> nodes;
|
||||
std::int64_t sourceLabel = 0;
|
||||
if (tryPositiveInteger(load.target, sourceLabel)) {
|
||||
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
|
||||
if (domain.nodes()[node].sourceId.source_label == sourceLabel) {
|
||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||
if (domain.Nodes()[node].source_id.source_label == sourceLabel) {
|
||||
nodes.push_back(static_cast<EntityIndex>(node));
|
||||
}
|
||||
}
|
||||
@@ -404,9 +404,9 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
|
||||
"A station-eligibility load target must resolve unambiguously.");
|
||||
}
|
||||
if (!sets.empty()) {
|
||||
std::vector<unsigned char> seen(domain.nodes().size(), 0U);
|
||||
for (const EntityIndex node : sets.front()->nodeIndices) {
|
||||
if (node >= domain.nodes().size() || seen[node] != 0U) {
|
||||
std::vector<unsigned char> seen(domain.Nodes().size(), 0U);
|
||||
for (const EntityIndex node : sets.front()->node_indices) {
|
||||
if (node >= domain.Nodes().size() || seen[node] != 0U) {
|
||||
return recoveryResultFailure<std::vector<EntityIndex>>(
|
||||
"invalid-node-station-entity",
|
||||
load.location,
|
||||
@@ -416,7 +416,7 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
|
||||
seen[node] = 1U;
|
||||
}
|
||||
return Result<std::vector<EntityIndex>>::Success(
|
||||
sets.front()->nodeIndices);
|
||||
sets.front()->node_indices);
|
||||
}
|
||||
if (!nodes.empty()) {
|
||||
return Result<std::vector<EntityIndex>>::Success(std::move(nodes));
|
||||
@@ -504,7 +504,7 @@ Status populateShellGlobalEvidence(
|
||||
double reactionForceScale = 0.0;
|
||||
double appliedMomentScale = 0.0;
|
||||
double reactionMomentScale = 0.0;
|
||||
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
|
||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||
const std::size_t offset = node * kDofsPerNode;
|
||||
std::array<double, 3> nodalAppliedForce{};
|
||||
std::array<double, 3> nodalReactionForce{};
|
||||
@@ -524,9 +524,9 @@ Status populateShellGlobalEvidence(
|
||||
}
|
||||
|
||||
const auto appliedForceMoment =
|
||||
cross(domain.nodes()[node].coordinates, nodalAppliedForce);
|
||||
cross(domain.Nodes()[node].coordinates, nodalAppliedForce);
|
||||
const auto reactionForceMoment =
|
||||
cross(domain.nodes()[node].coordinates, nodalReactionForce);
|
||||
cross(domain.Nodes()[node].coordinates, nodalReactionForce);
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
nodalAppliedMoment[component] += appliedForceMoment[component];
|
||||
nodalReactionMoment[component] += reactionForceMoment[component];
|
||||
@@ -541,8 +541,8 @@ Status populateShellGlobalEvidence(
|
||||
reactionMoment, reactionMomentScale, nodalReactionMoment)) {
|
||||
return recoveryFailure(
|
||||
"nonfinite-recovery-value",
|
||||
domain.nodes()[node].location,
|
||||
domain.nodes()[node].sourceId.source_label_text,
|
||||
domain.Nodes()[node].location,
|
||||
domain.Nodes()[node].source_id.source_label_text,
|
||||
"Global force and moment evidence must remain finite in source-node order.");
|
||||
}
|
||||
}
|
||||
@@ -569,7 +569,7 @@ Status populateShellGlobalEvidence(
|
||||
!finite(candidate.verificationMetrics)) {
|
||||
return recoveryFailure(
|
||||
"nonfinite-recovery-value",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
"global-equilibrium",
|
||||
"Global equilibrium values and their physical normalization scales must be finite.");
|
||||
}
|
||||
@@ -577,7 +577,7 @@ Status populateShellGlobalEvidence(
|
||||
momentMetric > kGlobalEquilibriumTolerance) {
|
||||
return recoveryFailure(
|
||||
"global-equilibrium-tolerance-failure",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
"global-equilibrium",
|
||||
"Normalized global force or moment balance exceeds 1e-10.");
|
||||
}
|
||||
@@ -586,8 +586,8 @@ Status populateShellGlobalEvidence(
|
||||
|
||||
std::optional<AxisSet> localAxes(const Domain& domain,
|
||||
const EulerBeam3DDefinition& element) {
|
||||
const auto& first = domain.nodes()[element.nodeIndices[0U]].coordinates;
|
||||
const auto& second = domain.nodes()[element.nodeIndices[1U]].coordinates;
|
||||
const auto& first = domain.Nodes()[element.node_indices[0U]].coordinates;
|
||||
const auto& second = domain.Nodes()[element.node_indices[1U]].coordinates;
|
||||
const std::array<double, 3> delta = {
|
||||
second[0U] - first[0U],
|
||||
second[1U] - first[1U],
|
||||
@@ -598,7 +598,7 @@ std::optional<AxisSet> localAxes(const Domain& domain,
|
||||
}
|
||||
const std::array<double, 3> ex = {
|
||||
delta[0U] / length, delta[1U] / length, delta[2U] / length};
|
||||
const auto& guide = domain.sections()[element.sectionIndex].firstAxis;
|
||||
const auto& guide = domain.Sections()[element.section_index].first_axis;
|
||||
const double projection = dot(guide, ex);
|
||||
const std::array<double, 3> eyTrial = {
|
||||
guide[0U] - projection * ex[0U],
|
||||
@@ -654,8 +654,8 @@ Status ResultRecovery::recover(const AnalysisModel& model,
|
||||
if (!finite(internalForce)) {
|
||||
return recoveryFailure(
|
||||
"nonfinite-recovery-value",
|
||||
{model.domain().sourcePath(), 0U},
|
||||
model.domain().sourceContentIdentity(),
|
||||
{model.domain().SourcePath(), 0U},
|
||||
model.domain().SourceContentIdentity(),
|
||||
"Full stiffness multiplication must produce finite internal force.");
|
||||
}
|
||||
Vector residual{dofs.fullDofCount()};
|
||||
@@ -665,7 +665,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
|
||||
if (!std::isfinite(residual[fullDof])) {
|
||||
return recoveryFailure(
|
||||
"nonfinite-recovery-value",
|
||||
{model.domain().sourcePath(), 0U},
|
||||
{model.domain().SourcePath(), 0U},
|
||||
std::to_string(fullDof),
|
||||
"Internal-minus-external residual must remain finite.");
|
||||
}
|
||||
@@ -684,7 +684,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
|
||||
if (!std::isfinite(residualNorm) || !std::isfinite(denominator)) {
|
||||
return recoveryFailure(
|
||||
"nonfinite-recovery-value",
|
||||
{model.domain().sourcePath(), 0U},
|
||||
{model.domain().SourcePath(), 0U},
|
||||
"free-residual",
|
||||
"Free residual and its physical normalization scale must be finite.");
|
||||
}
|
||||
@@ -699,7 +699,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
|
||||
normalizedResidual > kFreeResidualTolerance) {
|
||||
return recoveryFailure(
|
||||
"free-residual-tolerance-failure",
|
||||
{model.domain().sourcePath(), 0U},
|
||||
{model.domain().SourcePath(), 0U},
|
||||
"free-residual",
|
||||
"The normalized free residual exceeds 1e-10.");
|
||||
}
|
||||
@@ -716,12 +716,12 @@ Status ResultRecovery::recover(const AnalysisModel& model,
|
||||
gaussRows.reserve(model.activeElements().size() * 2U);
|
||||
const Domain& domain = model.domain();
|
||||
for (const EntityIndex elementIndex : model.activeElements()) {
|
||||
const auto& definition = domain.elements()[elementIndex];
|
||||
auto beam = EulerBeam3D::create(
|
||||
domain.nodes()[definition.nodeIndices[0U]],
|
||||
domain.nodes()[definition.nodeIndices[1U]],
|
||||
domain.sections()[definition.sectionIndex],
|
||||
domain.materials()[definition.materialIndex]);
|
||||
const auto& definition = domain.Elements()[elementIndex];
|
||||
auto beam = EulerBeam3D::Create(
|
||||
domain.Nodes()[definition.node_indices[0U]],
|
||||
domain.Nodes()[definition.node_indices[1U]],
|
||||
domain.Sections()[definition.section_index],
|
||||
domain.Materials()[definition.material_index]);
|
||||
if (!beam.HasValue()) {
|
||||
return beam.GetStatus();
|
||||
}
|
||||
@@ -735,52 +735,52 @@ Status ResultRecovery::recover(const AnalysisModel& model,
|
||||
state.displacement()[scatter[localDof]];
|
||||
}
|
||||
const BeamRecovery recovered =
|
||||
beam.Value().recover(elementDisplacement);
|
||||
beam.Value().Recover(elementDisplacement);
|
||||
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
|
||||
if (!finite(recovered.equilibriumEndActions[endpoint]) ||
|
||||
!finite(recovered.endpointSectionResultants[endpoint])) {
|
||||
if (!finite(recovered.equilibrium_end_actions[endpoint]) ||
|
||||
!finite(recovered.endpoint_section_resultants[endpoint])) {
|
||||
return recoveryFailure(
|
||||
"nonfinite-recovery-value",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Endpoint recovery values must be finite.");
|
||||
}
|
||||
endpointRows.push_back({
|
||||
elementIndex,
|
||||
static_cast<int>(endpoint),
|
||||
domain.nodes()[definition.nodeIndices[endpoint]].sourceId,
|
||||
recovered.equilibriumEndActions[endpoint],
|
||||
recovered.endpointSectionResultants[endpoint]});
|
||||
domain.Nodes()[definition.node_indices[endpoint]].source_id,
|
||||
recovered.equilibrium_end_actions[endpoint],
|
||||
recovered.endpoint_section_resultants[endpoint]});
|
||||
}
|
||||
for (std::size_t gauss = 0U; gauss < 2U; ++gauss) {
|
||||
if (!finite(recovered.gaussGeneralizedStrains[gauss]) ||
|
||||
!finite(recovered.gaussGeneralizedResultants[gauss])) {
|
||||
if (!finite(recovered.gauss_generalized_strains[gauss]) ||
|
||||
!finite(recovered.gauss_generalized_resultants[gauss])) {
|
||||
return recoveryFailure(
|
||||
"nonfinite-recovery-value",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Gauss recovery values must be finite.");
|
||||
}
|
||||
gaussRows.push_back({
|
||||
elementIndex,
|
||||
static_cast<int>(gauss + 1U),
|
||||
recovered.gaussGeneralizedStrains[gauss],
|
||||
recovered.gaussGeneralizedResultants[gauss]});
|
||||
recovered.gauss_generalized_strains[gauss],
|
||||
recovered.gauss_generalized_resultants[gauss]});
|
||||
}
|
||||
for (const auto& point : recovered.stressPoints) {
|
||||
if ((point.gaussPoint != 1 && point.gaussPoint != 2) ||
|
||||
for (const auto& point : recovered.stress_points) {
|
||||
if ((point.gauss_point != 1 && point.gauss_point != 2) ||
|
||||
!std::isfinite(point.x1) || !std::isfinite(point.x2) ||
|
||||
!std::isfinite(point.s11)) {
|
||||
return recoveryFailure(
|
||||
"nonfinite-recovery-value",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Stress recovery identity and values must be finite and ordered.");
|
||||
}
|
||||
stressRows.push_back({
|
||||
elementIndex,
|
||||
point.gaussPoint,
|
||||
point.sectionPoint,
|
||||
point.gauss_point,
|
||||
point.section_point,
|
||||
point.x1,
|
||||
point.x2,
|
||||
point.s11,
|
||||
@@ -790,33 +790,33 @@ Status ResultRecovery::recover(const AnalysisModel& model,
|
||||
|
||||
ShellStateCandidate shellCandidate{};
|
||||
std::vector<EntityIndex> expectedShellElements;
|
||||
if (!domain.shellElements().empty()) {
|
||||
if (domain.shellElements().size() >
|
||||
if (!domain.ShellElements().empty()) {
|
||||
if (domain.ShellElements().size() >
|
||||
(std::numeric_limits<std::size_t>::max)() /
|
||||
kShellLocationCount) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-dimensions",
|
||||
{domain.sourcePath(), 0U},
|
||||
domain.sourceContentIdentity(),
|
||||
{domain.SourcePath(), 0U},
|
||||
domain.SourceContentIdentity(),
|
||||
"The shell result-row inventory exceeds the addressable range.");
|
||||
}
|
||||
std::vector<std::optional<std::array<double, 3>>> directorsByNode(
|
||||
domain.nodes().size());
|
||||
for (const auto& frame : domain.shellNodeInitialFrames()) {
|
||||
if (frame.nodeIndex >= directorsByNode.size() ||
|
||||
directorsByNode[frame.nodeIndex].has_value()) {
|
||||
domain.Nodes().size());
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
if (frame.node_index >= directorsByNode.size() ||
|
||||
directorsByNode[frame.node_index].has_value()) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-entity",
|
||||
{domain.sourcePath(), 0U},
|
||||
std::to_string(frame.nodeIndex),
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(frame.node_index),
|
||||
"Shell initial directors must map uniquely to model nodes.");
|
||||
}
|
||||
directorsByNode[frame.nodeIndex] = frame.director;
|
||||
directorsByNode[frame.node_index] = frame.director;
|
||||
}
|
||||
|
||||
shellCandidate.rows.reserve(
|
||||
domain.shellElements().size() * kShellLocationCount);
|
||||
expectedShellElements.reserve(domain.shellElements().size());
|
||||
domain.ShellElements().size() * kShellLocationCount);
|
||||
expectedShellElements.reserve(domain.ShellElements().size());
|
||||
constexpr std::array<ShellMidsurfaceLocation, kShellLocationCount>
|
||||
locations{
|
||||
ShellMidsurfaceLocation::gp1,
|
||||
@@ -829,33 +829,33 @@ Status ResultRecovery::recover(const AnalysisModel& model,
|
||||
ShellSectionPosition::top};
|
||||
constexpr std::array<double, 3> zeta{-1.0, 0.0, 1.0};
|
||||
for (std::size_t elementOrder = 0U;
|
||||
elementOrder < domain.shellElements().size();
|
||||
elementOrder < domain.ShellElements().size();
|
||||
++elementOrder) {
|
||||
const EntityIndex elementIndex =
|
||||
static_cast<EntityIndex>(elementOrder);
|
||||
const auto& definition = domain.shellElements()[elementOrder];
|
||||
const auto& definition = domain.ShellElements()[elementOrder];
|
||||
std::array<const Node*, 4> nodes{};
|
||||
std::array<std::array<double, 3>, 4> directors{};
|
||||
for (std::size_t nodePosition = 0U;
|
||||
nodePosition < definition.nodeIndices.size();
|
||||
nodePosition < definition.node_indices.size();
|
||||
++nodePosition) {
|
||||
const EntityIndex node = definition.nodeIndices[nodePosition];
|
||||
const EntityIndex node = definition.node_indices[nodePosition];
|
||||
if (!directorsByNode[node].has_value()) {
|
||||
return recoveryFailure(
|
||||
"invalid-recovery-entity",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Shell recovery requires one initial director per element node.");
|
||||
}
|
||||
nodes[nodePosition] = &domain.nodes()[node];
|
||||
nodes[nodePosition] = &domain.Nodes()[node];
|
||||
directors[nodePosition] = *directorsByNode[node];
|
||||
}
|
||||
|
||||
auto shell = Mitc4Shell::create(
|
||||
auto shell = Mitc4Shell::Create(
|
||||
nodes,
|
||||
directors,
|
||||
domain.shellSections()[definition.sectionIndex],
|
||||
domain.materials()[definition.materialIndex]);
|
||||
domain.ShellSections()[definition.section_index],
|
||||
domain.Materials()[definition.material_index]);
|
||||
if (!shell.HasValue()) {
|
||||
return shell.GetStatus();
|
||||
}
|
||||
@@ -867,19 +867,19 @@ Status ResultRecovery::recover(const AnalysisModel& model,
|
||||
elementDisplacement[localDof] =
|
||||
state.displacement()[scatter[localDof]];
|
||||
}
|
||||
auto recovered = shell.Value().recoverPhysical(
|
||||
auto recovered = shell.Value().RecoverPhysical(
|
||||
elementDisplacement);
|
||||
if (!recovered.HasValue()) {
|
||||
return recovered.GetStatus();
|
||||
}
|
||||
const double accumulatedEnergy =
|
||||
shellCandidate.physicalStrainEnergy +
|
||||
recovered.Value().strainEnergy;
|
||||
recovered.Value().strain_energy;
|
||||
if (!std::isfinite(accumulatedEnergy)) {
|
||||
return recoveryFailure(
|
||||
"nonfinite-recovery-value",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Source-order physical shell energy reduction must remain finite.");
|
||||
}
|
||||
shellCandidate.physicalStrainEnergy = accumulatedEnergy;
|
||||
@@ -892,20 +892,20 @@ Status ResultRecovery::recover(const AnalysisModel& model,
|
||||
ShellResultRow row{};
|
||||
row.element = elementIndex;
|
||||
row.location = locations[point];
|
||||
row.naturalCoordinates = physicalPoint.naturalCoordinates;
|
||||
row.naturalCoordinates = physicalPoint.natural_coordinates;
|
||||
row.localFrame = {
|
||||
physicalPoint.localFrame.e1,
|
||||
physicalPoint.localFrame.e2,
|
||||
physicalPoint.localFrame.e3};
|
||||
row.generalizedStrain = physicalPoint.generalizedStrain;
|
||||
row.sectionResultant = physicalPoint.sectionResultant;
|
||||
physicalPoint.local_frame.e1,
|
||||
physicalPoint.local_frame.e2,
|
||||
physicalPoint.local_frame.e3};
|
||||
row.generalizedStrain = physicalPoint.generalized_strain;
|
||||
row.sectionResultant = physicalPoint.section_resultant;
|
||||
for (std::size_t position = 0U;
|
||||
position < positions.size();
|
||||
++position) {
|
||||
row.stress[position] = {
|
||||
positions[position],
|
||||
zeta[position],
|
||||
physicalPoint.inPlaneStress[position]};
|
||||
physicalPoint.in_plane_stress[position]};
|
||||
}
|
||||
shellCandidate.rows.push_back(std::move(row));
|
||||
}
|
||||
@@ -951,7 +951,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
|
||||
if (!std::isfinite(tolerance) || tolerance < 0.0) {
|
||||
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"invalid-node-station-tolerance",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
"component-tolerances",
|
||||
"Node-station component tolerances must be finite and nonnegative.");
|
||||
}
|
||||
@@ -961,50 +961,50 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
|
||||
endpointRows.size() != model.activeElements().size() * 2U) {
|
||||
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"invalid-node-station-shape",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(endpointRows.size()),
|
||||
"Endpoint rows must contain exactly two rows per active element.");
|
||||
}
|
||||
|
||||
std::vector<std::vector<const EndpointResultRow*>> rowsByNode(
|
||||
domain.nodes().size());
|
||||
domain.Nodes().size());
|
||||
for (std::size_t order = 0U;
|
||||
order < model.activeElements().size();
|
||||
++order) {
|
||||
const EntityIndex elementIndex = model.activeElements()[order];
|
||||
if (elementIndex >= domain.elements().size()) {
|
||||
if (elementIndex >= domain.Elements().size()) {
|
||||
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"invalid-node-station-entity",
|
||||
{domain.sourcePath(), 0U},
|
||||
{domain.SourcePath(), 0U},
|
||||
std::to_string(elementIndex),
|
||||
"Every active station element must be a valid stable entity.");
|
||||
}
|
||||
const auto& definition = domain.elements()[elementIndex];
|
||||
const auto& definition = domain.Elements()[elementIndex];
|
||||
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
|
||||
const auto& row = endpointRows[order * 2U + endpoint];
|
||||
const EntityIndex nodeIndex = definition.nodeIndices[endpoint];
|
||||
if (nodeIndex >= domain.nodes().size() ||
|
||||
const EntityIndex nodeIndex = definition.node_indices[endpoint];
|
||||
if (nodeIndex >= domain.Nodes().size() ||
|
||||
row.element != elementIndex ||
|
||||
row.endpoint != static_cast<int>(endpoint) ||
|
||||
!sameSourceIdentity(row.node, domain.nodes()[nodeIndex].sourceId)) {
|
||||
!sameSourceIdentity(row.node, domain.Nodes()[nodeIndex].source_id)) {
|
||||
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"invalid-node-station-entity",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Endpoint rows must preserve active element, endpoint, and source-node order.");
|
||||
}
|
||||
if (!finite(row.sectionResultant)) {
|
||||
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"nonfinite-node-station-value",
|
||||
definition.location,
|
||||
definition.sourceId.source_label_text,
|
||||
definition.source_id.source_label_text,
|
||||
"Node-station section resultants must be finite.");
|
||||
}
|
||||
rowsByNode[nodeIndex].push_back(&row);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned char> loadedNodes(domain.nodes().size(), 0U);
|
||||
std::vector<unsigned char> loadedNodes(domain.Nodes().size(), 0U);
|
||||
if (model.activeLoads().size() != model.step().loads.size()) {
|
||||
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"invalid-node-station-entity",
|
||||
@@ -1042,7 +1042,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
|
||||
}
|
||||
|
||||
std::vector<NodeStationResultRow> stations;
|
||||
stations.reserve(domain.nodes().size());
|
||||
stations.reserve(domain.Nodes().size());
|
||||
for (std::size_t nodeIndex = 0U;
|
||||
nodeIndex < rowsByNode.size();
|
||||
++nodeIndex) {
|
||||
@@ -1052,7 +1052,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
|
||||
}
|
||||
if (incident.size() == 1U) {
|
||||
stations.push_back({
|
||||
domain.nodes()[nodeIndex].sourceId,
|
||||
domain.Nodes()[nodeIndex].source_id,
|
||||
incident.front()->element,
|
||||
incident.front()->sectionResultant});
|
||||
continue;
|
||||
@@ -1060,13 +1060,13 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
|
||||
if (incident.size() != 2U || loadedNodes[nodeIndex] != 0U) {
|
||||
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"ineligible-node-station",
|
||||
domain.nodes()[nodeIndex].location,
|
||||
domain.nodes()[nodeIndex].sourceId.source_label_text,
|
||||
domain.Nodes()[nodeIndex].location,
|
||||
domain.Nodes()[nodeIndex].source_id.source_label_text,
|
||||
"Interior station collapse requires exactly two unloaded endpoints.");
|
||||
}
|
||||
|
||||
const auto& firstElement = domain.elements()[incident[0U]->element];
|
||||
const auto& secondElement = domain.elements()[incident[1U]->element];
|
||||
const auto& firstElement = domain.Elements()[incident[0U]->element];
|
||||
const auto& secondElement = domain.Elements()[incident[1U]->element];
|
||||
const bool chainOrientation =
|
||||
incident[0U]->endpoint != incident[1U]->endpoint &&
|
||||
((incident[0U]->endpoint == 1 && incident[1U]->endpoint == 0) ||
|
||||
@@ -1074,13 +1074,13 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
|
||||
const auto firstAxes = localAxes(domain, firstElement);
|
||||
const auto secondAxes = localAxes(domain, secondElement);
|
||||
if (!chainOrientation ||
|
||||
firstElement.sectionIndex != secondElement.sectionIndex ||
|
||||
firstElement.section_index != secondElement.section_index ||
|
||||
!firstAxes.has_value() || !secondAxes.has_value() ||
|
||||
!sameAxes(*firstAxes, *secondAxes)) {
|
||||
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"ineligible-node-station",
|
||||
domain.nodes()[nodeIndex].location,
|
||||
domain.nodes()[nodeIndex].sourceId.source_label_text,
|
||||
domain.Nodes()[nodeIndex].location,
|
||||
domain.Nodes()[nodeIndex].source_id.source_label_text,
|
||||
"Interior station endpoints require one consistent section and local-axis chain.");
|
||||
}
|
||||
|
||||
@@ -1096,15 +1096,15 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
|
||||
if (!std::isfinite(difference)) {
|
||||
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"nonfinite-node-station-value",
|
||||
domain.nodes()[nodeIndex].location,
|
||||
domain.nodes()[nodeIndex].sourceId.source_label_text,
|
||||
domain.Nodes()[nodeIndex].location,
|
||||
domain.Nodes()[nodeIndex].source_id.source_label_text,
|
||||
"Endpoint comparison must produce a finite difference.");
|
||||
}
|
||||
if (difference > componentTolerances[component]) {
|
||||
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
|
||||
"node-station-tolerance-failure",
|
||||
domain.nodes()[nodeIndex].location,
|
||||
domain.nodes()[nodeIndex].sourceId.source_label_text,
|
||||
domain.Nodes()[nodeIndex].location,
|
||||
domain.Nodes()[nodeIndex].source_id.source_label_text,
|
||||
"Interior endpoint resultants disagree beyond component tolerance.");
|
||||
}
|
||||
}
|
||||
@@ -1114,7 +1114,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
|
||||
? incident[0U]
|
||||
: incident[1U];
|
||||
stations.push_back({
|
||||
domain.nodes()[nodeIndex].sourceId,
|
||||
domain.Nodes()[nodeIndex].source_id,
|
||||
representative->element,
|
||||
representative->sectionResultant});
|
||||
}
|
||||
|
||||
@@ -307,8 +307,8 @@ public:
|
||||
const fesa::AnalysisState& state,
|
||||
const std::vector<fesa::Diagnostic>& diagnostics) override {
|
||||
outputPath_ = outputPath;
|
||||
nodeCount_ = domain.nodes().size();
|
||||
shellElementCount_ = domain.shellElements().size();
|
||||
nodeCount_ = domain.Nodes().size();
|
||||
shellElementCount_ = domain.ShellElements().size();
|
||||
state_ = std::make_unique<fesa::AnalysisState>(state);
|
||||
diagnostics_ = diagnostics;
|
||||
return fesa::Status::Ok();
|
||||
|
||||
@@ -785,9 +785,9 @@ void requireFiniteStress(const hid_t file) {
|
||||
|
||||
std::array<double, 9> expectedLocalAxes(
|
||||
const Domain& domain, const EulerBeam3DDefinition& element) {
|
||||
const auto& first = domain.nodes()[element.nodeIndices[0U]].coordinates;
|
||||
const auto& second = domain.nodes()[element.nodeIndices[1U]].coordinates;
|
||||
const auto& guide = domain.sections()[element.sectionIndex].firstAxis;
|
||||
const auto& first = domain.Nodes()[element.node_indices[0U]].coordinates;
|
||||
const auto& second = domain.Nodes()[element.node_indices[1U]].coordinates;
|
||||
const auto& guide = domain.Sections()[element.section_index].first_axis;
|
||||
const std::array<double, 3> delta = {
|
||||
second[0U] - first[0U],
|
||||
second[1U] - first[1U],
|
||||
@@ -862,7 +862,7 @@ HdfProjection readHdfProjection(
|
||||
std::filesystem::absolute(input).lexically_normal().generic_u8string();
|
||||
const std::string expectedIdentity =
|
||||
"path=" + normalizedInput +
|
||||
";content_identity=" + domain.sourceContentIdentity();
|
||||
";content_identity=" + domain.SourceContentIdentity();
|
||||
if (sourceIdentity != expectedIdentity) {
|
||||
fail("schema-mismatch", "The HDF5 source-input identity is inconsistent.");
|
||||
}
|
||||
@@ -870,30 +870,30 @@ HdfProjection readHdfProjection(
|
||||
HdfProjection projection{};
|
||||
projection.nodes = readNodeRows(file.get());
|
||||
projection.elements = readElementRows(file.get());
|
||||
if (projection.nodes.size() != domain.nodes().size() ||
|
||||
projection.elements.size() != domain.elements().size()) {
|
||||
if (projection.nodes.size() != domain.Nodes().size() ||
|
||||
projection.elements.size() != domain.Elements().size()) {
|
||||
fail("schema-mismatch", "HDF5 model identity counts do not match the input.");
|
||||
}
|
||||
for (std::size_t node = 0U; node < projection.nodes.size(); ++node) {
|
||||
const auto& actual = projection.nodes[node];
|
||||
const auto& expected = domain.nodes()[node];
|
||||
const auto& expected = domain.Nodes()[node];
|
||||
if (actual.internalNodeId != node ||
|
||||
actual.instanceName != expected.sourceId.instance_name ||
|
||||
actual.sourceNodeLabel != expected.sourceId.source_label ||
|
||||
actual.sourceNodeLabelText != expected.sourceId.source_label_text ||
|
||||
actual.instanceName != expected.source_id.instance_name ||
|
||||
actual.sourceNodeLabel != expected.source_id.source_label ||
|
||||
actual.sourceNodeLabelText != expected.source_id.source_label_text ||
|
||||
actual.coordinates != expected.coordinates) {
|
||||
fail("schema-mismatch", "An HDF5 node identity does not match the input.");
|
||||
}
|
||||
}
|
||||
for (std::size_t element = 0U; element < projection.elements.size(); ++element) {
|
||||
const auto& actual = projection.elements[element];
|
||||
const auto& expected = domain.elements()[element];
|
||||
const auto& expected = domain.Elements()[element];
|
||||
if (actual.internalElementId != element ||
|
||||
actual.instanceName != expected.sourceId.instance_name ||
|
||||
actual.sourceElementLabel != expected.sourceId.source_label ||
|
||||
actual.sourceElementLabelText != expected.sourceId.source_label_text ||
|
||||
actual.nodeInternalIds[0U] != expected.nodeIndices[0U] ||
|
||||
actual.nodeInternalIds[1U] != expected.nodeIndices[1U]) {
|
||||
actual.instanceName != expected.source_id.instance_name ||
|
||||
actual.sourceElementLabel != expected.source_id.source_label ||
|
||||
actual.sourceElementLabelText != expected.source_id.source_label_text ||
|
||||
actual.nodeInternalIds[0U] != expected.node_indices[0U] ||
|
||||
actual.nodeInternalIds[1U] != expected.node_indices[1U]) {
|
||||
fail("schema-mismatch", "An HDF5 element identity does not match the input.");
|
||||
}
|
||||
const auto axes = expectedLocalAxes(domain, expected);
|
||||
@@ -986,7 +986,7 @@ std::vector<NodeStationResultRow> normalizeStations(
|
||||
endpoints.push_back({
|
||||
static_cast<EntityIndex>(element),
|
||||
static_cast<int>(endpoint),
|
||||
domain.nodes()[node].sourceId,
|
||||
domain.Nodes()[node].source_id,
|
||||
{},
|
||||
values});
|
||||
}
|
||||
@@ -1192,8 +1192,8 @@ PhysicsEvidence makePhysicsEvidence(
|
||||
}
|
||||
evidence.freeResidualNorm =
|
||||
std::sqrt(static_cast<double>(residualSquared));
|
||||
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
|
||||
const auto& coordinates = domain.nodes()[node].coordinates;
|
||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||
const auto& coordinates = domain.Nodes()[node].coordinates;
|
||||
const std::array<double, 3> applied = {
|
||||
load[node * 6U + 0U],
|
||||
load[node * 6U + 1U],
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/io/abaqus/input_reader.hpp"
|
||||
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -173,8 +173,8 @@ fesa::ModelDefinition makeDefinition(
|
||||
const std::filesystem::path& input,
|
||||
std::string sourceContentIdentity) {
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = input;
|
||||
definition.sourceContentIdentity = std::move(sourceContentIdentity);
|
||||
definition.source_path = input;
|
||||
definition.source_content_identity = std::move(sourceContentIdentity);
|
||||
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
||||
const auto label = static_cast<std::int64_t>(node + 1U);
|
||||
definition.nodes.push_back({
|
||||
@@ -217,7 +217,7 @@ void writeResultsFixture(
|
||||
if (!parsedInput.HasValue()) {
|
||||
throw std::runtime_error{"Reference fixture input identity read failed."};
|
||||
}
|
||||
auto domainResult = fesa::Domain::create(
|
||||
auto domainResult = fesa::Domain::Create(
|
||||
makeDefinition(input, parsedInput.Value().sourceContentIdentity));
|
||||
if (!domainResult.HasValue()) {
|
||||
throw std::runtime_error{"Reference fixture Domain construction failed."};
|
||||
@@ -250,7 +250,7 @@ void writeResultsFixture(
|
||||
state.endpointResults().push_back({
|
||||
static_cast<fesa::EntityIndex>(element),
|
||||
static_cast<int>(endpoint),
|
||||
domain.nodes()[node].sourceId,
|
||||
domain.Nodes()[node].source_id,
|
||||
{},
|
||||
values.sectionResultants[element][endpoint]});
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "fesa/analysis/analysis_model.hpp"
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -33,8 +33,8 @@ LoadFixture makeFixture(
|
||||
std::vector<fesa::NodalLoad> loads) {
|
||||
const std::filesystem::path source{"models/load-assembly.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:abcdef0123456789";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:abcdef0123456789";
|
||||
for (std::size_t index = 0U; index < nodeCount; ++index) {
|
||||
const auto label = static_cast<std::int64_t>((index + 1U) * 10U);
|
||||
definition.nodes.push_back({
|
||||
@@ -42,7 +42,7 @@ LoadFixture makeFixture(
|
||||
{static_cast<double>(index), 0.0, 0.0},
|
||||
{source, index + 2U}});
|
||||
}
|
||||
definition.nodeSets = std::move(nodeSets);
|
||||
definition.node_sets = std::move(nodeSets);
|
||||
definition.steps = {{
|
||||
"Step-1",
|
||||
std::move(boundaries),
|
||||
@@ -53,7 +53,7 @@ LoadFixture makeFixture(
|
||||
1.0,
|
||||
{source, 20U}}};
|
||||
|
||||
auto domainResult = fesa::Domain::create(std::move(definition));
|
||||
auto domainResult = fesa::Domain::Create(std::move(definition));
|
||||
if (!domainResult.HasValue()) {
|
||||
throw std::runtime_error{"Load fixture Domain construction failed."};
|
||||
}
|
||||
@@ -81,8 +81,8 @@ LoadFixture makeShellFixture(
|
||||
std::vector<fesa::NodalLoad> loads) {
|
||||
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:1122334455667788";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:1122334455667788";
|
||||
definition.nodes = {
|
||||
{{"Shell-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 2U}},
|
||||
{{"Shell-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 3U}},
|
||||
@@ -90,17 +90,17 @@ LoadFixture makeShellFixture(
|
||||
{{"Shell-1", 40, "40"}, {0.0, 1.0, 0.0}, {source, 5U}}};
|
||||
definition.materials = {
|
||||
{"Material", 1000.0, 0.25, {source, 6U}}};
|
||||
definition.shellSections = {
|
||||
definition.shell_sections = {
|
||||
{"ShellSection", 0.1, 0U, {source, 7U}}};
|
||||
definition.shellElements = {{
|
||||
definition.shell_elements = {{
|
||||
{"Shell-1", 1, "1"},
|
||||
fesa::ShellSourceElementType::s4,
|
||||
fesa::ShellSourceElementType::kS4,
|
||||
{0U, 1U, 2U, 3U},
|
||||
0U,
|
||||
0U,
|
||||
{source, 8U}}};
|
||||
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
||||
definition.shellNodeInitialFrames.push_back({
|
||||
definition.shell_node_initial_frames.push_back({
|
||||
static_cast<fesa::EntityIndex>(node),
|
||||
{0.0, 0.0, 1.0},
|
||||
{1.0, 0.0, 0.0},
|
||||
@@ -116,7 +116,7 @@ LoadFixture makeShellFixture(
|
||||
1.0,
|
||||
{source, 20U}}};
|
||||
|
||||
auto domainResult = fesa::Domain::create(std::move(definition));
|
||||
auto domainResult = fesa::Domain::Create(std::move(definition));
|
||||
if (!domainResult.HasValue()) {
|
||||
throw std::runtime_error{"Shell load fixture Domain construction failed."};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "fesa/analysis/analysis_model.hpp"
|
||||
#include "fesa/assembly/parallel_for.hpp"
|
||||
#include "fesa/assembly/sparse_assembler.hpp"
|
||||
#include "fesa/elements/mitc4_shell.hpp"
|
||||
#include "fesa/elements/mitc4_shell.h"
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace {
|
||||
fesa::ModelDefinition makeDefinition() {
|
||||
const std::filesystem::path source{"models/sparse-assembly.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||
definition.nodes = {
|
||||
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||
{{"Beam-1", 2, "2"}, {2.0, 0.0, 0.0}, {source, 11U}},
|
||||
@@ -50,8 +50,8 @@ fesa::ModelDefinition makeShellDefinition(
|
||||
const bool twoElements = false) {
|
||||
const std::filesystem::path source{"models/shell-sparse-assembly.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:fedcba9876543210";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:fedcba9876543210";
|
||||
if (twoElements) {
|
||||
definition.nodes = {
|
||||
{{"Shell-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||
@@ -61,13 +61,13 @@ fesa::ModelDefinition makeShellDefinition(
|
||||
{{"Shell-1", 5, "5"}, {1.0, 1.0, 0.0}, {source, 14U}},
|
||||
{{"Shell-1", 6, "6"}, {2.0, 1.0, 0.0}, {source, 15U}}};
|
||||
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
||||
definition.shellNodeInitialFrames.push_back({
|
||||
definition.shell_node_initial_frames.push_back({
|
||||
static_cast<fesa::EntityIndex>(node),
|
||||
{0.0, 0.0, 1.0},
|
||||
{1.0, 0.0, 0.0},
|
||||
{0.0, 1.0, 0.0}});
|
||||
}
|
||||
definition.shellElements = {
|
||||
definition.shell_elements = {
|
||||
{{"Shell-1", 10, "10"}, sourceType, {0U, 1U, 4U, 3U},
|
||||
0U, 0U, {source, 40U}},
|
||||
{{"Shell-1", 20, "20"}, sourceType, {1U, 2U, 5U, 4U},
|
||||
@@ -80,19 +80,19 @@ fesa::ModelDefinition makeShellDefinition(
|
||||
{{"Shell-1", 3, "3"}, {0.0, 1.0, 1.0}, {source, 12U}},
|
||||
{{"Shell-1", 4, "4"}, {0.0, 0.0, 1.0}, {source, 13U}}};
|
||||
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
||||
definition.shellNodeInitialFrames.push_back({
|
||||
definition.shell_node_initial_frames.push_back({
|
||||
static_cast<fesa::EntityIndex>(node),
|
||||
{1.0, 0.0, 0.0},
|
||||
{0.0, 1.0, 0.0},
|
||||
{0.0, 0.0, 1.0}});
|
||||
}
|
||||
definition.shellElements = {{
|
||||
definition.shell_elements = {{
|
||||
{"Shell-1", 10, "10"}, sourceType, {0U, 1U, 2U, 3U},
|
||||
0U, 0U, {source, 40U}}};
|
||||
}
|
||||
definition.materials = {
|
||||
{"Material", 120.0, 0.25, {source, 20U}}};
|
||||
definition.shellSections = {
|
||||
definition.shell_sections = {
|
||||
{"ShellSection", 0.2, 0U, {source, 30U}}};
|
||||
definition.steps = {{
|
||||
"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}};
|
||||
@@ -102,30 +102,30 @@ fesa::ModelDefinition makeShellDefinition(
|
||||
fesa::Result<fesa::Mitc4Stiffness> directShellStiffness(
|
||||
const fesa::Domain& domain,
|
||||
const fesa::EntityIndex elementIndex) {
|
||||
const auto& definition = domain.shellElements().at(elementIndex);
|
||||
const auto& definition = domain.ShellElements().at(elementIndex);
|
||||
std::array<const fesa::Node*, 4> nodes{};
|
||||
std::array<std::array<double, 3>, 4> directors{};
|
||||
for (std::size_t node = 0U; node < definition.nodeIndices.size(); ++node) {
|
||||
const fesa::EntityIndex nodeIndex = definition.nodeIndices[node];
|
||||
nodes[node] = &domain.nodes().at(nodeIndex);
|
||||
directors[node] = domain.shellNodeInitialFrames().at(nodeIndex).director;
|
||||
for (std::size_t node = 0U; node < definition.node_indices.size(); ++node) {
|
||||
const fesa::EntityIndex nodeIndex = definition.node_indices[node];
|
||||
nodes[node] = &domain.Nodes().at(nodeIndex);
|
||||
directors[node] = domain.ShellNodeInitialFrames().at(nodeIndex).director;
|
||||
}
|
||||
auto shell = fesa::Mitc4Shell::create(
|
||||
auto shell = fesa::Mitc4Shell::Create(
|
||||
nodes,
|
||||
directors,
|
||||
domain.shellSections().at(definition.sectionIndex),
|
||||
domain.materials().at(definition.materialIndex));
|
||||
domain.ShellSections().at(definition.section_index),
|
||||
domain.Materials().at(definition.material_index));
|
||||
if (!shell.HasValue()) {
|
||||
return fesa::Result<fesa::Mitc4Stiffness>::Failure(shell.GetStatus());
|
||||
}
|
||||
return shell.Value().stiffness();
|
||||
return shell.Value().Stiffness();
|
||||
}
|
||||
|
||||
fesa::Result<fesa::SparseMatrix> assembleShell(
|
||||
const fesa::ShellSourceElementType sourceType,
|
||||
const fesa::ParallelFor& parallelFor,
|
||||
const bool twoElements = false) {
|
||||
auto domain = fesa::Domain::create(
|
||||
auto domain = fesa::Domain::Create(
|
||||
makeShellDefinition(sourceType, twoElements));
|
||||
if (!domain.HasValue()) {
|
||||
return fesa::Result<fesa::SparseMatrix>::Failure(domain.GetStatus());
|
||||
@@ -198,7 +198,7 @@ void expectByteIdentical(
|
||||
}
|
||||
|
||||
TEST(SparseAssembly, SerialTbbAndRepeatedRunsAreByteIdentical) {
|
||||
auto domainResult = fesa::Domain::create(makeDefinition());
|
||||
auto domainResult = fesa::Domain::Create(makeDefinition());
|
||||
ASSERT_TRUE(domainResult.HasValue());
|
||||
auto modelResult = fesa::AnalysisModel::create(domainResult.Value());
|
||||
ASSERT_TRUE(modelResult.HasValue());
|
||||
@@ -257,8 +257,8 @@ TEST(SparseAssembly, SerialTbbAndRepeatedRunsAreByteIdentical) {
|
||||
TEST(
|
||||
SparseAssembly,
|
||||
AssemblesFourNodeTwentyFourDofKernelAndPreservesDiagonalSlots) {
|
||||
auto domain = fesa::Domain::create(
|
||||
makeShellDefinition(fesa::ShellSourceElementType::s4));
|
||||
auto domain = fesa::Domain::Create(
|
||||
makeShellDefinition(fesa::ShellSourceElementType::kS4));
|
||||
ASSERT_TRUE(domain.HasValue());
|
||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
||||
ASSERT_TRUE(model.HasValue());
|
||||
@@ -290,7 +290,7 @@ TEST(
|
||||
for (std::size_t column = 0U; column < 24U; ++column) {
|
||||
EXPECT_DOUBLE_EQ(
|
||||
entry(assembled.Value(), row, column),
|
||||
expected.Value().stabilizedGlobal24(row, column));
|
||||
expected.Value().stabilized_global24(row, column));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,11 +300,11 @@ TEST(SparseAssembly, ShellSerialTbbReverseAndRepeatedRunsAreByteIdentical) {
|
||||
fesa::TbbParallelFor tbbExecutor;
|
||||
ReverseParallelFor reverseExecutor;
|
||||
auto serial = assembleShell(
|
||||
fesa::ShellSourceElementType::s4, serialExecutor, true);
|
||||
fesa::ShellSourceElementType::kS4, serialExecutor, true);
|
||||
auto tbb = assembleShell(
|
||||
fesa::ShellSourceElementType::s4, tbbExecutor, true);
|
||||
fesa::ShellSourceElementType::kS4, tbbExecutor, true);
|
||||
auto reversed = assembleShell(
|
||||
fesa::ShellSourceElementType::s4, reverseExecutor, true);
|
||||
fesa::ShellSourceElementType::kS4, reverseExecutor, true);
|
||||
ASSERT_TRUE(serial.HasValue());
|
||||
ASSERT_TRUE(tbb.HasValue());
|
||||
ASSERT_TRUE(reversed.HasValue());
|
||||
@@ -315,7 +315,7 @@ TEST(SparseAssembly, ShellSerialTbbReverseAndRepeatedRunsAreByteIdentical) {
|
||||
expectByteIdentical(reversed.Value(), serial.Value());
|
||||
for (std::size_t repetition = 0U; repetition < 8U; ++repetition) {
|
||||
auto repeated = assembleShell(
|
||||
fesa::ShellSourceElementType::s4, tbbExecutor, true);
|
||||
fesa::ShellSourceElementType::kS4, tbbExecutor, true);
|
||||
ASSERT_TRUE(repeated.HasValue());
|
||||
expectByteIdentical(repeated.Value(), serial.Value());
|
||||
}
|
||||
@@ -324,9 +324,9 @@ TEST(SparseAssembly, ShellSerialTbbReverseAndRepeatedRunsAreByteIdentical) {
|
||||
TEST(SparseAssembly, S4AndS4rSemanticFixturesAssembleIdenticalStiffness) {
|
||||
fesa::SerialParallelFor serialExecutor;
|
||||
auto s4 = assembleShell(
|
||||
fesa::ShellSourceElementType::s4, serialExecutor);
|
||||
fesa::ShellSourceElementType::kS4, serialExecutor);
|
||||
auto s4r = assembleShell(
|
||||
fesa::ShellSourceElementType::s4r, serialExecutor);
|
||||
fesa::ShellSourceElementType::kS4r, serialExecutor);
|
||||
ASSERT_TRUE(s4.HasValue());
|
||||
ASSERT_TRUE(s4r.HasValue());
|
||||
EXPECT_TRUE(std::any_of(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "fesa/analysis/analysis_model.hpp"
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -19,8 +19,8 @@ fesa::DofManager makeDofs(
|
||||
std::vector<fesa::BoundaryCondition> boundaries) {
|
||||
const std::filesystem::path source{"models/essential-constraints.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:1234567890abcdef";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:1234567890abcdef";
|
||||
definition.nodes = {
|
||||
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 2U}}};
|
||||
definition.steps = {{
|
||||
@@ -33,7 +33,7 @@ fesa::DofManager makeDofs(
|
||||
1.0,
|
||||
{source, 10U}}};
|
||||
|
||||
auto domain = fesa::Domain::create(std::move(definition));
|
||||
auto domain = fesa::Domain::Create(std::move(definition));
|
||||
EXPECT_TRUE(domain.HasValue());
|
||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
||||
EXPECT_TRUE(model.HasValue());
|
||||
@@ -46,8 +46,8 @@ fesa::DofManager makeShellSizedDofs(
|
||||
std::vector<fesa::BoundaryCondition> boundaries) {
|
||||
const std::filesystem::path source{"models/shell-essential-constraints.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:8877665544332211";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:8877665544332211";
|
||||
definition.nodes = {
|
||||
{{"Shell-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 2U}},
|
||||
{{"Shell-1", 2, "2"}, {1.0, 0.0, 0.0}, {source, 3U}},
|
||||
@@ -55,11 +55,11 @@ fesa::DofManager makeShellSizedDofs(
|
||||
{{"Shell-1", 4, "4"}, {0.0, 1.0, 0.0}, {source, 5U}}};
|
||||
definition.materials = {
|
||||
{"Material", 1000.0, 0.25, {source, 6U}}};
|
||||
definition.shellSections = {
|
||||
definition.shell_sections = {
|
||||
{"ShellSection", 0.1, 0U, {source, 7U}}};
|
||||
definition.shellElements = {{
|
||||
definition.shell_elements = {{
|
||||
{"Shell-1", 1, "1"},
|
||||
fesa::ShellSourceElementType::s4,
|
||||
fesa::ShellSourceElementType::kS4,
|
||||
{0U, 1U, 2U, 3U},
|
||||
0U,
|
||||
0U,
|
||||
@@ -74,7 +74,7 @@ fesa::DofManager makeShellSizedDofs(
|
||||
1.0,
|
||||
{source, 10U}}};
|
||||
|
||||
auto domain = fesa::Domain::create(std::move(definition));
|
||||
auto domain = fesa::Domain::Create(std::move(definition));
|
||||
EXPECT_TRUE(domain.HasValue());
|
||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
||||
EXPECT_TRUE(model.HasValue());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,8 @@ namespace {
|
||||
fesa::ModelDefinition makeDefinition() {
|
||||
const std::filesystem::path source{"models/dof-manager.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||
definition.nodes = {
|
||||
{{"Beam-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||
{{"Beam-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 11U}},
|
||||
@@ -28,7 +28,7 @@ fesa::ModelDefinition makeDefinition() {
|
||||
definition.elements = {
|
||||
{{"Beam-1", 100, "100"}, {0U, 1U}, 0U, 0U, {source, 40U}},
|
||||
{{"Beam-1", 200, "200"}, {1U, 2U}, 0U, 0U, {source, 41U}}};
|
||||
definition.nodeSets = {
|
||||
definition.node_sets = {
|
||||
{"Root", std::optional<std::string>{"Beam-1"}, {0U}, {source, 50U}},
|
||||
{"Ends", std::optional<std::string>{"Beam-1"}, {0U, 2U}, {source, 51U}}};
|
||||
definition.steps = {{
|
||||
@@ -49,8 +49,8 @@ fesa::ModelDefinition makeDefinition() {
|
||||
fesa::ModelDefinition makeShellDefinition() {
|
||||
const std::filesystem::path source{"models/shell-dof-manager.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:1122334455667788";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:1122334455667788";
|
||||
definition.nodes = {
|
||||
{{"Shell-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||
{{"Shell-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 11U}},
|
||||
@@ -58,11 +58,11 @@ fesa::ModelDefinition makeShellDefinition() {
|
||||
{{"Shell-1", 40, "40"}, {0.0, 1.0, 0.0}, {source, 13U}}};
|
||||
definition.materials = {
|
||||
{"Material", 1000.0, 0.25, {source, 20U}}};
|
||||
definition.shellSections = {
|
||||
definition.shell_sections = {
|
||||
{"ShellSection", 0.1, 0U, {source, 30U}}};
|
||||
definition.shellElements = {{
|
||||
definition.shell_elements = {{
|
||||
{"Shell-1", 100, "100"},
|
||||
fesa::ShellSourceElementType::s4,
|
||||
fesa::ShellSourceElementType::kS4,
|
||||
{2U, 0U, 3U, 1U},
|
||||
0U,
|
||||
0U,
|
||||
@@ -77,7 +77,7 @@ struct DofFixture {
|
||||
};
|
||||
|
||||
DofFixture makeDofFixture(fesa::ModelDefinition definition = makeDefinition()) {
|
||||
auto domain = fesa::Domain::create(std::move(definition));
|
||||
auto domain = fesa::Domain::Create(std::move(definition));
|
||||
EXPECT_TRUE(domain.HasValue());
|
||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
||||
EXPECT_TRUE(model.HasValue());
|
||||
@@ -131,8 +131,8 @@ TEST(DofManager, ExpandsAndValidatesPrescribedValues) {
|
||||
|
||||
auto conflictingDefinition = makeDefinition();
|
||||
conflictingDefinition.steps[0].boundaries.push_back(
|
||||
{"root", 1, 1, 1.0, {conflictingDefinition.sourcePath, 77U}});
|
||||
auto domain = fesa::Domain::create(std::move(conflictingDefinition));
|
||||
{"root", 1, 1, 1.0, {conflictingDefinition.source_path, 77U}});
|
||||
auto domain = fesa::Domain::Create(std::move(conflictingDefinition));
|
||||
ASSERT_TRUE(domain.HasValue());
|
||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
||||
ASSERT_TRUE(model.HasValue());
|
||||
|
||||
@@ -249,55 +249,55 @@ TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
const fesa::Domain& domain = result.Value();
|
||||
|
||||
ASSERT_EQ(domain.nodes().size(), 2U);
|
||||
EXPECT_EQ(domain.nodes()[0].sourceId.instance_name, "Beam-1");
|
||||
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 1);
|
||||
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "0001");
|
||||
EXPECT_EQ(domain.nodes()[1].sourceId.source_label_text, "0002");
|
||||
EXPECT_DOUBLE_EQ(domain.nodes()[1].coordinates[0], 2.0);
|
||||
ASSERT_EQ(domain.Nodes().size(), 2U);
|
||||
EXPECT_EQ(domain.Nodes()[0].source_id.instance_name, "Beam-1");
|
||||
EXPECT_EQ(domain.Nodes()[0].source_id.source_label, 1);
|
||||
EXPECT_EQ(domain.Nodes()[0].source_id.source_label_text, "0001");
|
||||
EXPECT_EQ(domain.Nodes()[1].source_id.source_label_text, "0002");
|
||||
EXPECT_DOUBLE_EQ(domain.Nodes()[1].coordinates[0], 2.0);
|
||||
|
||||
ASSERT_EQ(domain.elements().size(), 1U);
|
||||
EXPECT_EQ(domain.elements()[0].sourceId.source_label_text, "0007");
|
||||
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U);
|
||||
EXPECT_EQ(domain.elements()[0].nodeIndices[1], 1U);
|
||||
ASSERT_EQ(domain.Elements().size(), 1U);
|
||||
EXPECT_EQ(domain.Elements()[0].source_id.source_label_text, "0007");
|
||||
EXPECT_EQ(domain.Elements()[0].node_indices[0], 0U);
|
||||
EXPECT_EQ(domain.Elements()[0].node_indices[1], 1U);
|
||||
|
||||
ASSERT_EQ(domain.materials().size(), 1U);
|
||||
EXPECT_EQ(domain.materials()[0].name, "Steel");
|
||||
EXPECT_DOUBLE_EQ(domain.materials()[0].youngsModulus, 210000.0);
|
||||
EXPECT_DOUBLE_EQ(domain.materials()[0].poissonRatio, 0.75);
|
||||
ASSERT_EQ(domain.Materials().size(), 1U);
|
||||
EXPECT_EQ(domain.Materials()[0].name, "Steel");
|
||||
EXPECT_DOUBLE_EQ(domain.Materials()[0].youngs_modulus, 210000.0);
|
||||
EXPECT_DOUBLE_EQ(domain.Materials()[0].poisson_ratio, 0.75);
|
||||
|
||||
ASSERT_EQ(domain.sections().size(), 1U);
|
||||
const auto& section = domain.sections()[0];
|
||||
ASSERT_EQ(domain.Sections().size(), 1U);
|
||||
const auto& section = domain.Sections()[0];
|
||||
EXPECT_DOUBLE_EQ(section.area, 2.0);
|
||||
EXPECT_DOUBLE_EQ(section.i11, 3.0);
|
||||
EXPECT_DOUBLE_EQ(section.i12, 0.0);
|
||||
EXPECT_DOUBLE_EQ(section.i22, 4.0);
|
||||
EXPECT_DOUBLE_EQ(section.torsionalConstant, 5.0);
|
||||
EXPECT_EQ(section.firstAxis, (std::array<double, 3>{0.0, 1.0, 0.0}));
|
||||
EXPECT_DOUBLE_EQ(section.torsional_constant, 5.0);
|
||||
EXPECT_EQ(section.first_axis, (std::array<double, 3>{0.0, 1.0, 0.0}));
|
||||
EXPECT_EQ(
|
||||
section.sectionPoints,
|
||||
section.section_points,
|
||||
(std::vector<std::array<double, 2>>{{-0.5, 0.25}, {0.5, -0.25}}));
|
||||
EXPECT_EQ(domain.elements()[0].materialIndex, 0U);
|
||||
EXPECT_EQ(domain.elements()[0].sectionIndex, 0U);
|
||||
EXPECT_EQ(domain.Elements()[0].material_index, 0U);
|
||||
EXPECT_EQ(domain.Elements()[0].section_index, 0U);
|
||||
|
||||
ASSERT_EQ(domain.steps().size(), 1U);
|
||||
const auto& step = domain.steps()[0];
|
||||
ASSERT_EQ(domain.Steps().size(), 1U);
|
||||
const auto& step = domain.Steps()[0];
|
||||
EXPECT_EQ(step.name, "Step-1");
|
||||
EXPECT_DOUBLE_EQ(step.initialIncrement, 0.25);
|
||||
EXPECT_DOUBLE_EQ(step.timePeriod, 1.5);
|
||||
EXPECT_DOUBLE_EQ(step.minimumIncrement, 0.01);
|
||||
EXPECT_DOUBLE_EQ(step.maximumIncrement, 1.5);
|
||||
EXPECT_DOUBLE_EQ(step.initial_increment, 0.25);
|
||||
EXPECT_DOUBLE_EQ(step.time_period, 1.5);
|
||||
EXPECT_DOUBLE_EQ(step.minimum_increment, 0.01);
|
||||
EXPECT_DOUBLE_EQ(step.maximum_increment, 1.5);
|
||||
ASSERT_EQ(step.boundaries.size(), 2U);
|
||||
EXPECT_EQ(step.boundaries[0].target, "rootassembly");
|
||||
EXPECT_EQ(step.boundaries[0].firstDof, 1);
|
||||
EXPECT_EQ(step.boundaries[0].first_dof, 1);
|
||||
EXPECT_DOUBLE_EQ(step.boundaries[0].value, 0.125);
|
||||
EXPECT_EQ(step.boundaries[1].lastDof, 2);
|
||||
EXPECT_EQ(step.boundaries[1].last_dof, 2);
|
||||
EXPECT_DOUBLE_EQ(step.boundaries[1].value, 0.0);
|
||||
ASSERT_EQ(step.loads.size(), 1U);
|
||||
EXPECT_EQ(step.loads[0].target, "RootAssembly");
|
||||
EXPECT_EQ(step.loads[0].dof, 6);
|
||||
EXPECT_DOUBLE_EQ(step.loads[0].magnitude, -12.5);
|
||||
EXPECT_EQ(domain.warnings().size(), 8U);
|
||||
EXPECT_EQ(domain.Warnings().size(), 8U);
|
||||
|
||||
const auto legacyPath = repositoryRoot() / "reference" /
|
||||
"cantilever beam" / "cantilever beam.inp";
|
||||
@@ -307,12 +307,12 @@ TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
|
||||
ASSERT_TRUE(parsedLegacy.HasValue());
|
||||
auto legacy = fesa::AbaqusDomainMapper{}.map(parsedLegacy.Value());
|
||||
ASSERT_TRUE(legacy.HasValue());
|
||||
EXPECT_EQ(legacy.Value().nodes().size(), 11U);
|
||||
EXPECT_EQ(legacy.Value().elements().size(), 10U);
|
||||
EXPECT_EQ(legacy.Value().materials().size(), 1U);
|
||||
EXPECT_EQ(legacy.Value().sections().size(), 1U);
|
||||
EXPECT_EQ(legacy.Value().steps().size(), 1U);
|
||||
EXPECT_EQ(legacy.Value().warnings().size(), 7U);
|
||||
EXPECT_EQ(legacy.Value().Nodes().size(), 11U);
|
||||
EXPECT_EQ(legacy.Value().Elements().size(), 10U);
|
||||
EXPECT_EQ(legacy.Value().Materials().size(), 1U);
|
||||
EXPECT_EQ(legacy.Value().Sections().size(), 1U);
|
||||
EXPECT_EQ(legacy.Value().Steps().size(), 1U);
|
||||
EXPECT_EQ(legacy.Value().Warnings().size(), 7U);
|
||||
EXPECT_EQ(readExactBytes(legacyPath), bytesBefore);
|
||||
EXPECT_EQ(std::filesystem::last_write_time(legacyPath), timestampBefore);
|
||||
}
|
||||
@@ -359,52 +359,52 @@ OnlySecond, 2, 5.
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
const fesa::Domain& domain = result.Value();
|
||||
|
||||
ASSERT_EQ(domain.nodes().size(), 4U);
|
||||
EXPECT_EQ(domain.nodes()[0].sourceId.instance_name, "First");
|
||||
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 10);
|
||||
EXPECT_EQ(domain.nodes()[1].sourceId.instance_name, "First");
|
||||
EXPECT_EQ(domain.nodes()[1].sourceId.source_label, 20);
|
||||
EXPECT_EQ(domain.nodes()[2].sourceId.instance_name, "Second");
|
||||
EXPECT_EQ(domain.nodes()[2].sourceId.source_label, 10);
|
||||
EXPECT_EQ(domain.nodes()[3].sourceId.instance_name, "Second");
|
||||
EXPECT_EQ(domain.nodes()[3].sourceId.source_label, 20);
|
||||
ASSERT_EQ(domain.Nodes().size(), 4U);
|
||||
EXPECT_EQ(domain.Nodes()[0].source_id.instance_name, "First");
|
||||
EXPECT_EQ(domain.Nodes()[0].source_id.source_label, 10);
|
||||
EXPECT_EQ(domain.Nodes()[1].source_id.instance_name, "First");
|
||||
EXPECT_EQ(domain.Nodes()[1].source_id.source_label, 20);
|
||||
EXPECT_EQ(domain.Nodes()[2].source_id.instance_name, "Second");
|
||||
EXPECT_EQ(domain.Nodes()[2].source_id.source_label, 10);
|
||||
EXPECT_EQ(domain.Nodes()[3].source_id.instance_name, "Second");
|
||||
EXPECT_EQ(domain.Nodes()[3].source_id.source_label, 20);
|
||||
|
||||
ASSERT_EQ(domain.elements().size(), 2U);
|
||||
EXPECT_EQ(domain.elements()[0].sourceId.instance_name, "First");
|
||||
EXPECT_EQ(domain.elements()[0].nodeIndices,
|
||||
ASSERT_EQ(domain.Elements().size(), 2U);
|
||||
EXPECT_EQ(domain.Elements()[0].source_id.instance_name, "First");
|
||||
EXPECT_EQ(domain.Elements()[0].node_indices,
|
||||
(std::array<fesa::EntityIndex, 2>{0U, 1U}));
|
||||
EXPECT_EQ(domain.elements()[1].sourceId.instance_name, "Second");
|
||||
EXPECT_EQ(domain.elements()[1].nodeIndices,
|
||||
EXPECT_EQ(domain.Elements()[1].source_id.instance_name, "Second");
|
||||
EXPECT_EQ(domain.Elements()[1].node_indices,
|
||||
(std::array<fesa::EntityIndex, 2>{2U, 3U}));
|
||||
|
||||
ASSERT_EQ(domain.nodeSets().size(), 3U);
|
||||
EXPECT_EQ(domain.nodeSets()[0].name, "Ends");
|
||||
EXPECT_EQ(domain.nodeSets()[0].instanceName, std::optional<std::string>{"First"});
|
||||
EXPECT_EQ(domain.nodeSets()[0].nodeIndices,
|
||||
ASSERT_EQ(domain.NodeSets().size(), 3U);
|
||||
EXPECT_EQ(domain.NodeSets()[0].name, "Ends");
|
||||
EXPECT_EQ(domain.NodeSets()[0].instance_name, std::optional<std::string>{"First"});
|
||||
EXPECT_EQ(domain.NodeSets()[0].node_indices,
|
||||
(std::vector<fesa::EntityIndex>{0U, 1U}));
|
||||
EXPECT_EQ(domain.nodeSets()[1].instanceName, std::optional<std::string>{"Second"});
|
||||
EXPECT_EQ(domain.nodeSets()[1].nodeIndices,
|
||||
EXPECT_EQ(domain.NodeSets()[1].instance_name, std::optional<std::string>{"Second"});
|
||||
EXPECT_EQ(domain.NodeSets()[1].node_indices,
|
||||
(std::vector<fesa::EntityIndex>{2U, 3U}));
|
||||
EXPECT_EQ(domain.nodeSets()[2].name, "OnlySecond");
|
||||
EXPECT_EQ(domain.nodeSets()[2].nodeIndices,
|
||||
EXPECT_EQ(domain.NodeSets()[2].name, "OnlySecond");
|
||||
EXPECT_EQ(domain.NodeSets()[2].node_indices,
|
||||
(std::vector<fesa::EntityIndex>{3U}));
|
||||
|
||||
ASSERT_EQ(domain.elementSets().size(), 3U);
|
||||
EXPECT_EQ(domain.elementSets()[0].instanceName,
|
||||
ASSERT_EQ(domain.ElementSets().size(), 3U);
|
||||
EXPECT_EQ(domain.ElementSets()[0].instance_name,
|
||||
std::optional<std::string>{"First"});
|
||||
EXPECT_EQ(domain.elementSets()[0].elementIndices,
|
||||
EXPECT_EQ(domain.ElementSets()[0].element_indices,
|
||||
(std::vector<fesa::EntityIndex>{0U}));
|
||||
EXPECT_EQ(domain.elementSets()[1].instanceName,
|
||||
EXPECT_EQ(domain.ElementSets()[1].instance_name,
|
||||
std::optional<std::string>{"Second"});
|
||||
EXPECT_EQ(domain.elementSets()[1].elementIndices,
|
||||
EXPECT_EQ(domain.ElementSets()[1].element_indices,
|
||||
(std::vector<fesa::EntityIndex>{1U}));
|
||||
EXPECT_EQ(domain.elementSets()[2].name, "OnlySecondBeam");
|
||||
EXPECT_EQ(domain.elementSets()[2].elementIndices,
|
||||
EXPECT_EQ(domain.ElementSets()[2].name, "OnlySecondBeam");
|
||||
EXPECT_EQ(domain.ElementSets()[2].element_indices,
|
||||
(std::vector<fesa::EntityIndex>{1U}));
|
||||
|
||||
ASSERT_EQ(domain.steps().size(), 1U);
|
||||
EXPECT_EQ(domain.steps()[0].boundaries[0].target, "OnlySecond");
|
||||
EXPECT_EQ(domain.steps()[0].loads[0].target, "OnlySecond");
|
||||
ASSERT_EQ(domain.Steps().size(), 1U);
|
||||
EXPECT_EQ(domain.Steps()[0].boundaries[0].target, "OnlySecond");
|
||||
EXPECT_EQ(domain.Steps()[0].loads[0].target, "OnlySecond");
|
||||
|
||||
auto direct = mapText(
|
||||
"direct-node-labels",
|
||||
@@ -413,8 +413,8 @@ OnlySecond, 2, 5.
|
||||
"Tip, 2, -1.",
|
||||
"2, 2, -1."));
|
||||
ASSERT_TRUE(direct.HasValue());
|
||||
EXPECT_EQ(direct.Value().steps()[0].boundaries[0].target, "1");
|
||||
EXPECT_EQ(direct.Value().steps()[0].loads[0].target, "2");
|
||||
EXPECT_EQ(direct.Value().Steps()[0].boundaries[0].target, "1");
|
||||
EXPECT_EQ(direct.Value().Steps()[0].loads[0].target, "2");
|
||||
|
||||
auto aboveThresholds = mapText(
|
||||
"above-geometry-thresholds",
|
||||
@@ -447,10 +447,10 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
|
||||
ASSERT_TRUE(sharedName.HasValue());
|
||||
const auto& domain = sharedName.Value();
|
||||
EXPECT_TRUE(std::any_of(
|
||||
domain.nodeSets().begin(), domain.nodeSets().end(),
|
||||
domain.NodeSets().begin(), domain.NodeSets().end(),
|
||||
[](const fesa::NodeSet& set) { return set.name == "ShellS4"; }));
|
||||
EXPECT_TRUE(std::any_of(
|
||||
domain.elementSets().begin(), domain.elementSets().end(),
|
||||
domain.ElementSets().begin(), domain.ElementSets().end(),
|
||||
[](const fesa::ElementSet& set) { return set.name == "ShellS4"; }));
|
||||
|
||||
auto duplicateNodeSet = mapText(
|
||||
@@ -486,16 +486,16 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
|
||||
ASSERT_TRUE(assemblySharedName.HasValue());
|
||||
const auto& assemblyDomain = assemblySharedName.Value();
|
||||
const auto rootNodeSetCount = std::count_if(
|
||||
assemblyDomain.nodeSets().begin(), assemblyDomain.nodeSets().end(),
|
||||
assemblyDomain.NodeSets().begin(), assemblyDomain.NodeSets().end(),
|
||||
[](const fesa::NodeSet& set) { return set.name == "Root"; });
|
||||
EXPECT_EQ(rootNodeSetCount, 1);
|
||||
const auto rootNodeSet = std::find_if(
|
||||
assemblyDomain.nodeSets().begin(), assemblyDomain.nodeSets().end(),
|
||||
assemblyDomain.NodeSets().begin(), assemblyDomain.NodeSets().end(),
|
||||
[](const fesa::NodeSet& set) { return set.name == "Root"; });
|
||||
ASSERT_NE(rootNodeSet, assemblyDomain.nodeSets().end());
|
||||
EXPECT_EQ(rootNodeSet->nodeIndices, (std::vector<fesa::EntityIndex>{0U}));
|
||||
ASSERT_NE(rootNodeSet, assemblyDomain.NodeSets().end());
|
||||
EXPECT_EQ(rootNodeSet->node_indices, (std::vector<fesa::EntityIndex>{0U}));
|
||||
EXPECT_EQ(std::count_if(
|
||||
assemblyDomain.elementSets().begin(), assemblyDomain.elementSets().end(),
|
||||
assemblyDomain.ElementSets().begin(), assemblyDomain.ElementSets().end(),
|
||||
[](const fesa::ElementSet& set) { return set.name == "Root"; }), 1);
|
||||
}
|
||||
|
||||
@@ -505,33 +505,33 @@ TEST(InpDomainMapping, NoOpAllowlistWarnsWithoutSemanticEffect) {
|
||||
ASSERT_TRUE(plain.HasValue());
|
||||
ASSERT_TRUE(withNoOps.HasValue());
|
||||
|
||||
EXPECT_TRUE(plain.Value().warnings().empty());
|
||||
ASSERT_EQ(withNoOps.Value().warnings().size(), 8U);
|
||||
EXPECT_EQ(withNoOps.Value().warnings()[0].code, "ignored-input-keyword");
|
||||
EXPECT_EQ(withNoOps.Value().warnings()[0].keyword, "PREPRINT");
|
||||
EXPECT_EQ(withNoOps.Value().warnings()[1].keyword,
|
||||
EXPECT_TRUE(plain.Value().Warnings().empty());
|
||||
ASSERT_EQ(withNoOps.Value().Warnings().size(), 8U);
|
||||
EXPECT_EQ(withNoOps.Value().Warnings()[0].code, "ignored-input-keyword");
|
||||
EXPECT_EQ(withNoOps.Value().Warnings()[0].keyword, "PREPRINT");
|
||||
EXPECT_EQ(withNoOps.Value().Warnings()[1].keyword,
|
||||
"TRANSVERSE SHEAR STIFFNESS");
|
||||
EXPECT_EQ(withNoOps.Value().warnings()[2].keyword, "RESTART");
|
||||
EXPECT_EQ(withNoOps.Value().warnings()[3].keyword, "OUTPUT");
|
||||
EXPECT_EQ(withNoOps.Value().warnings()[4].keyword, "NODE OUTPUT");
|
||||
EXPECT_EQ(withNoOps.Value().warnings()[5].keyword, "ELEMENT OUTPUT");
|
||||
EXPECT_EQ(withNoOps.Value().warnings()[6].keyword, "CONTACT OUTPUT");
|
||||
EXPECT_EQ(withNoOps.Value().warnings()[7].keyword, "OUTPUT");
|
||||
for (const auto& warning : withNoOps.Value().warnings()) {
|
||||
EXPECT_EQ(withNoOps.Value().Warnings()[2].keyword, "RESTART");
|
||||
EXPECT_EQ(withNoOps.Value().Warnings()[3].keyword, "OUTPUT");
|
||||
EXPECT_EQ(withNoOps.Value().Warnings()[4].keyword, "NODE OUTPUT");
|
||||
EXPECT_EQ(withNoOps.Value().Warnings()[5].keyword, "ELEMENT OUTPUT");
|
||||
EXPECT_EQ(withNoOps.Value().Warnings()[6].keyword, "CONTACT OUTPUT");
|
||||
EXPECT_EQ(withNoOps.Value().Warnings()[7].keyword, "OUTPUT");
|
||||
for (const auto& warning : withNoOps.Value().Warnings()) {
|
||||
EXPECT_EQ(warning.severity, fesa::Severity::kWarning);
|
||||
}
|
||||
|
||||
EXPECT_EQ(withNoOps.Value().nodes().size(), plain.Value().nodes().size());
|
||||
EXPECT_EQ(withNoOps.Value().elements().size(), plain.Value().elements().size());
|
||||
EXPECT_EQ(withNoOps.Value().materials().size(), plain.Value().materials().size());
|
||||
EXPECT_EQ(withNoOps.Value().sections().size(), plain.Value().sections().size());
|
||||
EXPECT_EQ(withNoOps.Value().nodeSets().size(), plain.Value().nodeSets().size());
|
||||
EXPECT_EQ(withNoOps.Value().elementSets().size(), plain.Value().elementSets().size());
|
||||
EXPECT_EQ(withNoOps.Value().steps().size(), plain.Value().steps().size());
|
||||
EXPECT_EQ(withNoOps.Value().steps()[0].boundaries.size(),
|
||||
plain.Value().steps()[0].boundaries.size());
|
||||
EXPECT_EQ(withNoOps.Value().steps()[0].loads.size(),
|
||||
plain.Value().steps()[0].loads.size());
|
||||
EXPECT_EQ(withNoOps.Value().Nodes().size(), plain.Value().Nodes().size());
|
||||
EXPECT_EQ(withNoOps.Value().Elements().size(), plain.Value().Elements().size());
|
||||
EXPECT_EQ(withNoOps.Value().Materials().size(), plain.Value().Materials().size());
|
||||
EXPECT_EQ(withNoOps.Value().Sections().size(), plain.Value().Sections().size());
|
||||
EXPECT_EQ(withNoOps.Value().NodeSets().size(), plain.Value().NodeSets().size());
|
||||
EXPECT_EQ(withNoOps.Value().ElementSets().size(), plain.Value().ElementSets().size());
|
||||
EXPECT_EQ(withNoOps.Value().Steps().size(), plain.Value().Steps().size());
|
||||
EXPECT_EQ(withNoOps.Value().Steps()[0].boundaries.size(),
|
||||
plain.Value().Steps()[0].boundaries.size());
|
||||
EXPECT_EQ(withNoOps.Value().Steps()[0].loads.size(),
|
||||
plain.Value().Steps()[0].loads.size());
|
||||
}
|
||||
|
||||
// MITC4-MAP-001
|
||||
@@ -540,63 +540,63 @@ TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
|
||||
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
const fesa::Domain& domain = result.Value();
|
||||
EXPECT_TRUE(domain.elements().empty());
|
||||
ASSERT_EQ(domain.shellElements().size(), 4U);
|
||||
EXPECT_EQ(domain.shellElements()[0].sourceId.instance_name, "First");
|
||||
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label_text, "0010");
|
||||
EXPECT_TRUE(domain.Elements().empty());
|
||||
ASSERT_EQ(domain.ShellElements().size(), 4U);
|
||||
EXPECT_EQ(domain.ShellElements()[0].source_id.instance_name, "First");
|
||||
EXPECT_EQ(domain.ShellElements()[0].source_id.source_label_text, "0010");
|
||||
EXPECT_EQ(
|
||||
domain.shellElements()[0].sourceType,
|
||||
fesa::ShellSourceElementType::s4);
|
||||
domain.ShellElements()[0].source_type,
|
||||
fesa::ShellSourceElementType::kS4);
|
||||
EXPECT_EQ(
|
||||
domain.shellElements()[0].nodeIndices,
|
||||
domain.ShellElements()[0].node_indices,
|
||||
(std::array<fesa::EntityIndex, 4>{0U, 1U, 2U, 3U}));
|
||||
EXPECT_EQ(domain.shellElements()[0].sectionIndex, 0U);
|
||||
EXPECT_EQ(domain.shellElements()[0].materialIndex, 0U);
|
||||
EXPECT_EQ(domain.ShellElements()[0].section_index, 0U);
|
||||
EXPECT_EQ(domain.ShellElements()[0].material_index, 0U);
|
||||
|
||||
EXPECT_EQ(domain.shellElements()[1].sourceId.instance_name, "First");
|
||||
EXPECT_EQ(domain.ShellElements()[1].source_id.instance_name, "First");
|
||||
EXPECT_EQ(
|
||||
domain.shellElements()[1].sourceType,
|
||||
fesa::ShellSourceElementType::s4r);
|
||||
domain.ShellElements()[1].source_type,
|
||||
fesa::ShellSourceElementType::kS4r);
|
||||
EXPECT_EQ(
|
||||
domain.shellElements()[1].nodeIndices,
|
||||
domain.ShellElements()[1].node_indices,
|
||||
(std::array<fesa::EntityIndex, 4>{1U, 4U, 5U, 2U}));
|
||||
EXPECT_EQ(domain.shellElements()[1].sectionIndex, 1U);
|
||||
EXPECT_EQ(domain.shellElements()[1].materialIndex, 1U);
|
||||
EXPECT_EQ(domain.ShellElements()[1].section_index, 1U);
|
||||
EXPECT_EQ(domain.ShellElements()[1].material_index, 1U);
|
||||
|
||||
EXPECT_EQ(domain.shellElements()[2].sourceId.instance_name, "Second");
|
||||
EXPECT_EQ(domain.ShellElements()[2].source_id.instance_name, "Second");
|
||||
EXPECT_EQ(
|
||||
domain.shellElements()[2].nodeIndices,
|
||||
domain.ShellElements()[2].node_indices,
|
||||
(std::array<fesa::EntityIndex, 4>{6U, 7U, 8U, 9U}));
|
||||
EXPECT_EQ(domain.shellElements()[3].sourceId.instance_name, "Second");
|
||||
EXPECT_EQ(domain.ShellElements()[3].source_id.instance_name, "Second");
|
||||
EXPECT_EQ(
|
||||
fesa::kMitc4InternalFormulation,
|
||||
std::string_view{"FESA-MITC4"});
|
||||
|
||||
ASSERT_EQ(domain.shellSections().size(), 2U);
|
||||
EXPECT_EQ(domain.shellSections()[0].name, "ShellS4");
|
||||
EXPECT_DOUBLE_EQ(domain.shellSections()[0].thickness, 0.1);
|
||||
EXPECT_EQ(domain.shellSections()[0].materialIndex, 0U);
|
||||
EXPECT_EQ(domain.shellSections()[1].name, "ShellS4R");
|
||||
EXPECT_DOUBLE_EQ(domain.shellSections()[1].thickness, 0.2);
|
||||
EXPECT_EQ(domain.shellSections()[1].materialIndex, 1U);
|
||||
ASSERT_EQ(domain.ShellSections().size(), 2U);
|
||||
EXPECT_EQ(domain.ShellSections()[0].name, "ShellS4");
|
||||
EXPECT_DOUBLE_EQ(domain.ShellSections()[0].thickness, 0.1);
|
||||
EXPECT_EQ(domain.ShellSections()[0].material_index, 0U);
|
||||
EXPECT_EQ(domain.ShellSections()[1].name, "ShellS4R");
|
||||
EXPECT_DOUBLE_EQ(domain.ShellSections()[1].thickness, 0.2);
|
||||
EXPECT_EQ(domain.ShellSections()[1].material_index, 1U);
|
||||
|
||||
ASSERT_EQ(domain.shellNodeInitialFrames().size(), domain.nodes().size());
|
||||
EXPECT_EQ(domain.shellNodeInitialFrames()[0].nodeIndex, 0U);
|
||||
ASSERT_EQ(domain.ShellNodeInitialFrames().size(), domain.Nodes().size());
|
||||
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].node_index, 0U);
|
||||
EXPECT_EQ(
|
||||
domain.shellNodeInitialFrames()[0].director,
|
||||
domain.ShellNodeInitialFrames()[0].director,
|
||||
(std::array<double, 3>{0.0, 0.0, 1.0}));
|
||||
EXPECT_EQ(
|
||||
domain.shellNodeInitialFrames()[0].tangentA,
|
||||
domain.ShellNodeInitialFrames()[0].tangent_a,
|
||||
(std::array<double, 3>{1.0, 0.0, 0.0}));
|
||||
EXPECT_EQ(
|
||||
domain.shellNodeInitialFrames()[0].tangentB,
|
||||
domain.ShellNodeInitialFrames()[0].tangent_b,
|
||||
(std::array<double, 3>{0.0, 1.0, 0.0}));
|
||||
|
||||
ASSERT_EQ(domain.steps().size(), 1U);
|
||||
ASSERT_EQ(domain.steps()[0].boundaries.size(), 1U);
|
||||
EXPECT_EQ(domain.steps()[0].boundaries[0].lastDof, 6);
|
||||
ASSERT_EQ(domain.steps()[0].loads.size(), 1U);
|
||||
EXPECT_EQ(domain.steps()[0].loads[0].dof, 6);
|
||||
ASSERT_EQ(domain.Steps().size(), 1U);
|
||||
ASSERT_EQ(domain.Steps()[0].boundaries.size(), 1U);
|
||||
EXPECT_EQ(domain.Steps()[0].boundaries[0].last_dof, 6);
|
||||
ASSERT_EQ(domain.Steps()[0].loads.size(), 1U);
|
||||
EXPECT_EQ(domain.Steps()[0].loads[0].dof, 6);
|
||||
}
|
||||
|
||||
// MITC4-MAP-002
|
||||
@@ -690,10 +690,10 @@ TEST(InpDomainMapping, PreservesProcedureLoadAndOutputRequestBoundariesForShells
|
||||
"*Output, field\n*Node Output\nU, RF\n*Element Output\nS\n*End Step\n");
|
||||
auto valid = mapText("mitc4-map-004-no-ops", withNoOps);
|
||||
ASSERT_TRUE(valid.HasValue());
|
||||
ASSERT_EQ(valid.Value().warnings().size(), 3U);
|
||||
EXPECT_EQ(valid.Value().warnings()[0].keyword, "OUTPUT");
|
||||
EXPECT_EQ(valid.Value().warnings()[1].keyword, "NODE OUTPUT");
|
||||
EXPECT_EQ(valid.Value().warnings()[2].keyword, "ELEMENT OUTPUT");
|
||||
ASSERT_EQ(valid.Value().Warnings().size(), 3U);
|
||||
EXPECT_EQ(valid.Value().Warnings()[0].keyword, "OUTPUT");
|
||||
EXPECT_EQ(valid.Value().Warnings()[1].keyword, "NODE OUTPUT");
|
||||
EXPECT_EQ(valid.Value().Warnings()[2].keyword, "ELEMENT OUTPUT");
|
||||
|
||||
struct InvalidCase {
|
||||
std::string name;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "fesa/analysis/analysis_state.hpp"
|
||||
#include "fesa/build_info.h"
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <hdf5.h>
|
||||
|
||||
@@ -123,8 +123,8 @@ fesa::ModelDefinition makeDefinition(
|
||||
const std::filesystem::path& source,
|
||||
const bool useDefaultCentroid) {
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||
definition.nodes = {
|
||||
{{u8"Beam-\u03b1", 101, "101"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||
{{u8"Beam-\u03b1", 202, "202"}, {3.0, 4.0, 0.0}, {source, 11U}}};
|
||||
@@ -156,7 +156,7 @@ fesa::ModelDefinition makeDefinition(
|
||||
WriterFixture makeFixture(
|
||||
const std::filesystem::path& source,
|
||||
const bool useDefaultCentroid = false) {
|
||||
auto domainResult = fesa::Domain::create(
|
||||
auto domainResult = fesa::Domain::Create(
|
||||
makeDefinition(source, useDefaultCentroid));
|
||||
if (!domainResult.HasValue()) {
|
||||
throw std::runtime_error{"Writer fixture Domain construction failed."};
|
||||
@@ -186,16 +186,16 @@ WriterFixture makeFixture(
|
||||
state->reaction()[index] = 100.0 + static_cast<double>(index);
|
||||
}
|
||||
|
||||
const auto& nodes = domain->nodes();
|
||||
const auto& nodes = domain->Nodes();
|
||||
state->endpointResults() = {
|
||||
{0U,
|
||||
0,
|
||||
nodes[0U].sourceId,
|
||||
nodes[0U].source_id,
|
||||
{1.0, 2.0, 3.0, 4.0, 5.0, 6.0},
|
||||
{11.0, 12.0, 13.0, 14.0}},
|
||||
{0U,
|
||||
1,
|
||||
nodes[1U].sourceId,
|
||||
nodes[1U].source_id,
|
||||
{7.0, 8.0, 9.0, 10.0, 11.0, 12.0},
|
||||
{15.0, 16.0, 17.0, 18.0}}};
|
||||
state->gaussResults() = {
|
||||
@@ -217,8 +217,8 @@ WriterFixture makeFixture(
|
||||
|
||||
fesa::ModelDefinition makeShellDefinition(const std::filesystem::path& source) {
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:fedcba9876543210";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:fedcba9876543210";
|
||||
definition.nodes = {
|
||||
{{"Shell-1", 11, "11"}, {-1.0, -1.0, 0.0}, {source, 10U}},
|
||||
{{"Shell-1", 12, "12"}, {1.0, -1.0, 0.0}, {source, 11U}},
|
||||
@@ -226,23 +226,23 @@ fesa::ModelDefinition makeShellDefinition(const std::filesystem::path& source) {
|
||||
{{"Shell-1", 14, "14"}, {-1.0, 1.0, 0.0}, {source, 13U}}};
|
||||
definition.materials = {
|
||||
{"ShellSteel", 210.0e9, 0.3, {source, 20U}}};
|
||||
definition.shellSections = {
|
||||
definition.shell_sections = {
|
||||
{"PlateSet", 0.02, 0U, {source, 30U}}};
|
||||
definition.shellElements = {{
|
||||
definition.shell_elements = {{
|
||||
{"Shell-1", 401, "401"},
|
||||
fesa::ShellSourceElementType::s4r,
|
||||
fesa::ShellSourceElementType::kS4r,
|
||||
{0U, 1U, 2U, 3U},
|
||||
0U,
|
||||
0U,
|
||||
{source, 40U}}};
|
||||
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
||||
definition.shellNodeInitialFrames.push_back({
|
||||
definition.shell_node_initial_frames.push_back({
|
||||
static_cast<fesa::EntityIndex>(node),
|
||||
{0.0, 0.0, 1.0},
|
||||
{1.0, 0.0, 0.0},
|
||||
{0.0, 1.0, 0.0}});
|
||||
}
|
||||
definition.nodeSets = {
|
||||
definition.node_sets = {
|
||||
{"Fixed", {}, {0U}, {source, 50U}}};
|
||||
definition.steps = {{
|
||||
"Step-1",
|
||||
@@ -258,7 +258,7 @@ fesa::ModelDefinition makeShellDefinition(const std::filesystem::path& source) {
|
||||
}
|
||||
|
||||
WriterFixture makeShellFixture(const std::filesystem::path& source) {
|
||||
auto domainResult = fesa::Domain::create(makeShellDefinition(source));
|
||||
auto domainResult = fesa::Domain::Create(makeShellDefinition(source));
|
||||
if (!domainResult.HasValue()) {
|
||||
throw std::runtime_error{"Shell writer fixture Domain construction failed."};
|
||||
}
|
||||
@@ -1033,7 +1033,7 @@ TEST(Hdf5ResultsWriter, WritesMandatoryOutputsDespiteOutputRequests) {
|
||||
const fesa::Diagnostic ignoredRequest{
|
||||
fesa::Severity::kWarning,
|
||||
"ignored-output-request",
|
||||
{fixture.domain->sourcePath(), 70U},
|
||||
{fixture.domain->SourcePath(), 70U},
|
||||
"*OUTPUT",
|
||||
"FIELD",
|
||||
"Abaqus output requests do not filter FESA mandatory results."};
|
||||
@@ -1065,13 +1065,13 @@ TEST(Hdf5ResultsWriter, WritesWarningsAndDefaultCentroid) {
|
||||
std::vector<fesa::Diagnostic> diagnostics = {
|
||||
{fesa::Severity::kWarning,
|
||||
"ignored-output-request",
|
||||
{fixture.domain->sourcePath(), 80U},
|
||||
{fixture.domain->SourcePath(), 80U},
|
||||
"*OUTPUT",
|
||||
"FIELD",
|
||||
"Ignored output request."},
|
||||
{fesa::Severity::kWarning,
|
||||
"ignored-keyword",
|
||||
{fixture.domain->sourcePath(), 20U},
|
||||
{fixture.domain->SourcePath(), 20U},
|
||||
"*PREPRINT",
|
||||
"",
|
||||
"Ignored generator control."}};
|
||||
@@ -1098,7 +1098,7 @@ TEST(Hdf5ResultsWriter, WritesWarningsAndDefaultCentroid) {
|
||||
EXPECT_STREQ(rows[0U].code, "ignored-keyword");
|
||||
EXPECT_STREQ(
|
||||
rows[0U].file,
|
||||
std::filesystem::absolute(fixture.domain->sourcePath())
|
||||
std::filesystem::absolute(fixture.domain->SourcePath())
|
||||
.lexically_normal()
|
||||
.generic_u8string()
|
||||
.c_str());
|
||||
@@ -1321,7 +1321,7 @@ TEST(Hdf5ResultsWriter, WritesShellInventoryDespiteRequestsAndOmitsForbiddenPath
|
||||
const fesa::Diagnostic ignoredRequest{
|
||||
fesa::Severity::kWarning,
|
||||
"ignored-output-request",
|
||||
{fixture.domain->sourcePath(), 80U},
|
||||
{fixture.domain->SourcePath(), 80U},
|
||||
"*ELEMENT OUTPUT",
|
||||
"S",
|
||||
"Output requests cannot filter mandatory shell results."};
|
||||
|
||||
+238
-224
@@ -1,4 +1,4 @@
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -10,254 +10,268 @@
|
||||
|
||||
namespace {
|
||||
|
||||
fesa::ModelDefinition makeOwnedDefinition() {
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = "models/owned.inp";
|
||||
definition.sourceContentIdentity = "fnv1a64:fedcba9876543210";
|
||||
definition.heading = "Stable declaration order";
|
||||
definition.nodes = {
|
||||
{{"Instance-A", 20, "0020"}, {2.0, 0.0, 0.0}, {"models/owned.inp", 12U}},
|
||||
{{"Instance-A", 10, "0010"}, {1.0, 0.0, 0.0}, {"models/owned.inp", 11U}}};
|
||||
definition.elements = {{
|
||||
{"Instance-A", 5, "0005"},
|
||||
{fesa::EntityIndex{1}, fesa::EntityIndex{0}},
|
||||
fesa::EntityIndex{0},
|
||||
fesa::EntityIndex{0},
|
||||
{"models/owned.inp", 20U}}};
|
||||
definition.materials = {{
|
||||
"Steel", 210.0e9, 0.3, {"models/owned.inp", 30U}}};
|
||||
definition.sections = {{
|
||||
"Section-1",
|
||||
0.02,
|
||||
1.0e-5,
|
||||
0.0,
|
||||
2.0e-5,
|
||||
5.0e-6,
|
||||
{0.0, 1.0, 0.0},
|
||||
{{-0.1, 0.0}, {0.1, 0.0}},
|
||||
{"models/owned.inp", 40U}}};
|
||||
definition.nodeSets = {{
|
||||
"Tip", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 45U}}};
|
||||
definition.elementSets = {{
|
||||
"Beam", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 46U}}};
|
||||
definition.parts = {{
|
||||
"BeamPart", {20, 10}, {5}, {"Tip"}, {"Beam"},
|
||||
{"models/owned.inp", 2U}}};
|
||||
definition.instances = {{
|
||||
"Instance-A",
|
||||
"BeamPart",
|
||||
{{20, 0U}, {10, 1U}},
|
||||
{{5, 0U}},
|
||||
{"models/owned.inp", 50U}}};
|
||||
definition.steps = {{
|
||||
"Step-1",
|
||||
{{"Root", 1, 6, 0.0, {"models/owned.inp", 60U}}},
|
||||
{{"Tip", 3, -1000.0, {"models/owned.inp", 61U}}},
|
||||
0.1,
|
||||
1.0,
|
||||
1.0e-5,
|
||||
1.0,
|
||||
{"models/owned.inp", 55U}}};
|
||||
definition.warnings = {{
|
||||
fesa::Severity::kWarning,
|
||||
"ignored-output-request",
|
||||
{"models/owned.inp", 70U},
|
||||
"*OUTPUT",
|
||||
"",
|
||||
"Ignored output request."}};
|
||||
return definition;
|
||||
fesa::ModelDefinition MakeOwnedDefinition() {
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.source_path = "models/owned.inp";
|
||||
definition.source_content_identity = "fnv1a64:fedcba9876543210";
|
||||
definition.heading = "Stable declaration order";
|
||||
definition.nodes = {
|
||||
{{"Instance-A", 20, "0020"}, {2.0, 0.0, 0.0}, {"models/owned.inp", 12U}},
|
||||
{{"Instance-A", 10, "0010"}, {1.0, 0.0, 0.0}, {"models/owned.inp", 11U}}};
|
||||
definition.elements = {{{"Instance-A", 5, "0005"},
|
||||
{fesa::EntityIndex{1}, fesa::EntityIndex{0}},
|
||||
fesa::EntityIndex{0},
|
||||
fesa::EntityIndex{0},
|
||||
{"models/owned.inp", 20U}}};
|
||||
definition.materials = {{"Steel", 210.0e9, 0.3, {"models/owned.inp", 30U}}};
|
||||
definition.sections = {{"Section-1",
|
||||
0.02,
|
||||
1.0e-5,
|
||||
0.0,
|
||||
2.0e-5,
|
||||
5.0e-6,
|
||||
{0.0, 1.0, 0.0},
|
||||
{{-0.1, 0.0}, {0.1, 0.0}},
|
||||
{"models/owned.inp", 40U}}};
|
||||
definition.node_sets = {
|
||||
{"Tip", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 45U}}};
|
||||
definition.element_sets = {
|
||||
{"Beam", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 46U}}};
|
||||
definition.parts = {
|
||||
{"BeamPart", {20, 10}, {5}, {"Tip"}, {"Beam"}, {"models/owned.inp", 2U}}};
|
||||
definition.instances = {{"Instance-A",
|
||||
"BeamPart",
|
||||
{{20, 0U}, {10, 1U}},
|
||||
{{5, 0U}},
|
||||
{"models/owned.inp", 50U}}};
|
||||
definition.steps = {{"Step-1",
|
||||
{{"Root", 1, 6, 0.0, {"models/owned.inp", 60U}}},
|
||||
{{"Tip", 3, -1000.0, {"models/owned.inp", 61U}}},
|
||||
0.1,
|
||||
1.0,
|
||||
1.0e-5,
|
||||
1.0,
|
||||
{"models/owned.inp", 55U}}};
|
||||
definition.warnings = {{fesa::Severity::kWarning,
|
||||
"ignored-output-request",
|
||||
{"models/owned.inp", 70U},
|
||||
"*OUTPUT",
|
||||
"",
|
||||
"Ignored output request."}};
|
||||
return definition;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) {
|
||||
auto definition = makeOwnedDefinition();
|
||||
auto result = fesa::Domain::create(definition);
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
auto definition = MakeOwnedDefinition();
|
||||
auto result = fesa::Domain::Create(definition);
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
|
||||
definition.sourcePath = "mutated.inp";
|
||||
definition.sourceContentIdentity = "mutated";
|
||||
definition.nodes[0].sourceId.source_label_text = "mutated";
|
||||
definition.nodes[0].coordinates[0] = -99.0;
|
||||
definition.sections[0].sectionPoints[0][0] = -99.0;
|
||||
definition.steps[0].loads[0].magnitude = 99.0;
|
||||
definition.warnings[0].code = "mutated";
|
||||
definition.source_path = "mutated.inp";
|
||||
definition.source_content_identity = "mutated";
|
||||
definition.nodes[0].source_id.source_label_text = "mutated";
|
||||
definition.nodes[0].coordinates[0] = -99.0;
|
||||
definition.sections[0].section_points[0][0] = -99.0;
|
||||
definition.steps[0].loads[0].magnitude = 99.0;
|
||||
definition.warnings[0].code = "mutated";
|
||||
|
||||
const fesa::Domain& domain = result.Value();
|
||||
const fesa::Node* const firstNodeAddress = domain.nodes().data();
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const fesa::Domain&>().nodes()),
|
||||
const std::vector<fesa::Node>&>);
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const fesa::Domain&>().elements()),
|
||||
const std::vector<fesa::EulerBeam3DDefinition>&>);
|
||||
const fesa::Domain& domain = result.Value();
|
||||
const fesa::Node* const first_node_address = domain.Nodes().data();
|
||||
static_assert(
|
||||
std::is_same_v<decltype(std::declval<const fesa::Domain&>().Nodes()),
|
||||
const std::vector<fesa::Node>&>);
|
||||
static_assert(
|
||||
std::is_same_v<decltype(std::declval<const fesa::Domain&>().Elements()),
|
||||
const std::vector<fesa::EulerBeam3DDefinition>&>);
|
||||
|
||||
EXPECT_EQ(domain.sourcePath(), std::filesystem::path{"models/owned.inp"});
|
||||
EXPECT_EQ(domain.sourceContentIdentity(), "fnv1a64:fedcba9876543210");
|
||||
ASSERT_EQ(domain.nodes().size(), 2U);
|
||||
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 20);
|
||||
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "0020");
|
||||
EXPECT_DOUBLE_EQ(domain.nodes()[0].coordinates[0], 2.0);
|
||||
EXPECT_EQ(domain.nodes()[1].sourceId.source_label, 10);
|
||||
EXPECT_EQ(domain.nodes().data(), firstNodeAddress);
|
||||
EXPECT_EQ(domain.SourcePath(), std::filesystem::path{"models/owned.inp"});
|
||||
EXPECT_EQ(domain.SourceContentIdentity(), "fnv1a64:fedcba9876543210");
|
||||
ASSERT_EQ(domain.Nodes().size(), 2U);
|
||||
EXPECT_EQ(domain.Nodes()[0].source_id.source_label, 20);
|
||||
EXPECT_EQ(domain.Nodes()[0].source_id.source_label_text, "0020");
|
||||
EXPECT_DOUBLE_EQ(domain.Nodes()[0].coordinates[0], 2.0);
|
||||
EXPECT_EQ(domain.Nodes()[1].source_id.source_label, 10);
|
||||
EXPECT_EQ(domain.Nodes().data(), first_node_address);
|
||||
|
||||
ASSERT_EQ(domain.elements().size(), 1U);
|
||||
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 1U);
|
||||
EXPECT_EQ(domain.elements()[0].nodeIndices[1], 0U);
|
||||
ASSERT_EQ(domain.materials().size(), 1U);
|
||||
ASSERT_EQ(domain.sections().size(), 1U);
|
||||
EXPECT_DOUBLE_EQ(domain.sections()[0].sectionPoints[0][0], -0.1);
|
||||
ASSERT_EQ(domain.nodeSets().size(), 1U);
|
||||
EXPECT_EQ(domain.nodeSets()[0].nodeIndices[0], 0U);
|
||||
ASSERT_EQ(domain.elementSets().size(), 1U);
|
||||
EXPECT_EQ(domain.elementSets()[0].elementIndices[0], 0U);
|
||||
ASSERT_EQ(domain.Elements().size(), 1U);
|
||||
EXPECT_EQ(domain.Elements()[0].node_indices[0], 1U);
|
||||
EXPECT_EQ(domain.Elements()[0].node_indices[1], 0U);
|
||||
ASSERT_EQ(domain.Materials().size(), 1U);
|
||||
ASSERT_EQ(domain.Sections().size(), 1U);
|
||||
EXPECT_DOUBLE_EQ(domain.Sections()[0].section_points[0][0], -0.1);
|
||||
ASSERT_EQ(domain.NodeSets().size(), 1U);
|
||||
EXPECT_EQ(domain.NodeSets()[0].node_indices[0], 0U);
|
||||
ASSERT_EQ(domain.ElementSets().size(), 1U);
|
||||
EXPECT_EQ(domain.ElementSets()[0].element_indices[0], 0U);
|
||||
|
||||
ASSERT_EQ(domain.steps().size(), 1U);
|
||||
EXPECT_EQ(domain.steps()[0].name, "Step-1");
|
||||
EXPECT_DOUBLE_EQ(domain.steps()[0].initialIncrement, 0.1);
|
||||
EXPECT_DOUBLE_EQ(domain.steps()[0].timePeriod, 1.0);
|
||||
EXPECT_DOUBLE_EQ(domain.steps()[0].minimumIncrement, 1.0e-5);
|
||||
EXPECT_DOUBLE_EQ(domain.steps()[0].maximumIncrement, 1.0);
|
||||
ASSERT_EQ(domain.steps()[0].boundaries.size(), 1U);
|
||||
ASSERT_EQ(domain.steps()[0].loads.size(), 1U);
|
||||
EXPECT_DOUBLE_EQ(domain.steps()[0].loads[0].magnitude, -1000.0);
|
||||
ASSERT_EQ(domain.Steps().size(), 1U);
|
||||
EXPECT_EQ(domain.Steps()[0].name, "Step-1");
|
||||
EXPECT_DOUBLE_EQ(domain.Steps()[0].initial_increment, 0.1);
|
||||
EXPECT_DOUBLE_EQ(domain.Steps()[0].time_period, 1.0);
|
||||
EXPECT_DOUBLE_EQ(domain.Steps()[0].minimum_increment, 1.0e-5);
|
||||
EXPECT_DOUBLE_EQ(domain.Steps()[0].maximum_increment, 1.0);
|
||||
ASSERT_EQ(domain.Steps()[0].boundaries.size(), 1U);
|
||||
ASSERT_EQ(domain.Steps()[0].loads.size(), 1U);
|
||||
EXPECT_DOUBLE_EQ(domain.Steps()[0].loads[0].magnitude, -1000.0);
|
||||
|
||||
ASSERT_EQ(domain.warnings().size(), 1U);
|
||||
EXPECT_EQ(domain.warnings()[0].code, "ignored-output-request");
|
||||
ASSERT_EQ(domain.Warnings().size(), 1U);
|
||||
EXPECT_EQ(domain.Warnings()[0].code, "ignored-output-request");
|
||||
}
|
||||
|
||||
TEST(DomainModel, MultipleIdentityInstancesDoNotMerge) {
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = "models/two-instances.inp";
|
||||
definition.sourceContentIdentity = "fnv1a64:0011223344556677";
|
||||
definition.nodes = {
|
||||
{{"Instance-A", 1, "1"}, {0.0, 0.0, 0.0}, {"models/two-instances.inp", 10U}},
|
||||
{{"Instance-A", 2, "2"}, {1.0, 0.0, 0.0}, {"models/two-instances.inp", 11U}},
|
||||
{{"Instance-B", 1, "1"}, {0.0, 0.0, 0.0}, {"models/two-instances.inp", 10U}},
|
||||
{{"Instance-B", 2, "2"}, {1.0, 0.0, 0.0}, {"models/two-instances.inp", 11U}}};
|
||||
definition.elements = {
|
||||
{{"Instance-A", 1, "1"}, {0U, 1U}, 0U, 0U, {"models/two-instances.inp", 20U}},
|
||||
{{"Instance-B", 1, "1"}, {2U, 3U}, 0U, 0U, {"models/two-instances.inp", 20U}}};
|
||||
definition.materials = {{
|
||||
"Steel", 210.0e9, 0.3, {"models/two-instances.inp", 30U}}};
|
||||
definition.sections = {{
|
||||
"Section-1",
|
||||
0.02,
|
||||
1.0e-5,
|
||||
0.0,
|
||||
2.0e-5,
|
||||
5.0e-6,
|
||||
{0.0, 1.0, 0.0},
|
||||
{},
|
||||
{"models/two-instances.inp", 40U}}};
|
||||
definition.parts = {{
|
||||
"BeamPart", {1, 2}, {1}, {}, {}, {"models/two-instances.inp", 2U}}};
|
||||
definition.instances = {
|
||||
{"Instance-A", "BeamPart", {{1, 0U}, {2, 1U}}, {{1, 0U}},
|
||||
{"models/two-instances.inp", 50U}},
|
||||
{"Instance-B", "BeamPart", {{1, 2U}, {2, 3U}}, {{1, 1U}},
|
||||
{"models/two-instances.inp", 51U}}};
|
||||
definition.steps = {{
|
||||
"Step-1", {}, {}, 0.1, 1.0, 1.0e-5, 1.0,
|
||||
{"models/two-instances.inp", 60U}}};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.source_path = "models/two-instances.inp";
|
||||
definition.source_content_identity = "fnv1a64:0011223344556677";
|
||||
definition.nodes = {{{"Instance-A", 1, "1"},
|
||||
{0.0, 0.0, 0.0},
|
||||
{"models/two-instances.inp", 10U}},
|
||||
{{"Instance-A", 2, "2"},
|
||||
{1.0, 0.0, 0.0},
|
||||
{"models/two-instances.inp", 11U}},
|
||||
{{"Instance-B", 1, "1"},
|
||||
{0.0, 0.0, 0.0},
|
||||
{"models/two-instances.inp", 10U}},
|
||||
{{"Instance-B", 2, "2"},
|
||||
{1.0, 0.0, 0.0},
|
||||
{"models/two-instances.inp", 11U}}};
|
||||
definition.elements = {{{"Instance-A", 1, "1"},
|
||||
{0U, 1U},
|
||||
0U,
|
||||
0U,
|
||||
{"models/two-instances.inp", 20U}},
|
||||
{{"Instance-B", 1, "1"},
|
||||
{2U, 3U},
|
||||
0U,
|
||||
0U,
|
||||
{"models/two-instances.inp", 20U}}};
|
||||
definition.materials = {
|
||||
{"Steel", 210.0e9, 0.3, {"models/two-instances.inp", 30U}}};
|
||||
definition.sections = {{"Section-1",
|
||||
0.02,
|
||||
1.0e-5,
|
||||
0.0,
|
||||
2.0e-5,
|
||||
5.0e-6,
|
||||
{0.0, 1.0, 0.0},
|
||||
{},
|
||||
{"models/two-instances.inp", 40U}}};
|
||||
definition.parts = {
|
||||
{"BeamPart", {1, 2}, {1}, {}, {}, {"models/two-instances.inp", 2U}}};
|
||||
definition.instances = {{"Instance-A",
|
||||
"BeamPart",
|
||||
{{1, 0U}, {2, 1U}},
|
||||
{{1, 0U}},
|
||||
{"models/two-instances.inp", 50U}},
|
||||
{"Instance-B",
|
||||
"BeamPart",
|
||||
{{1, 2U}, {2, 3U}},
|
||||
{{1, 1U}},
|
||||
{"models/two-instances.inp", 51U}}};
|
||||
definition.steps = {{"Step-1",
|
||||
{},
|
||||
{},
|
||||
0.1,
|
||||
1.0,
|
||||
1.0e-5,
|
||||
1.0,
|
||||
{"models/two-instances.inp", 60U}}};
|
||||
|
||||
auto result = fesa::Domain::create(std::move(definition));
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
const fesa::Domain& domain = result.Value();
|
||||
auto result = fesa::Domain::Create(std::move(definition));
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
const fesa::Domain& domain = result.Value();
|
||||
|
||||
ASSERT_EQ(domain.nodes().size(), 4U);
|
||||
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 1);
|
||||
EXPECT_EQ(domain.nodes()[2].sourceId.source_label, 1);
|
||||
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "1");
|
||||
EXPECT_EQ(domain.nodes()[2].sourceId.source_label_text, "1");
|
||||
EXPECT_NE(
|
||||
domain.nodes()[0].sourceId.instance_name,
|
||||
domain.nodes()[2].sourceId.instance_name);
|
||||
ASSERT_EQ(domain.Nodes().size(), 4U);
|
||||
EXPECT_EQ(domain.Nodes()[0].source_id.source_label, 1);
|
||||
EXPECT_EQ(domain.Nodes()[2].source_id.source_label, 1);
|
||||
EXPECT_EQ(domain.Nodes()[0].source_id.source_label_text, "1");
|
||||
EXPECT_EQ(domain.Nodes()[2].source_id.source_label_text, "1");
|
||||
EXPECT_NE(domain.Nodes()[0].source_id.instance_name,
|
||||
domain.Nodes()[2].source_id.instance_name);
|
||||
|
||||
ASSERT_EQ(domain.elements().size(), 2U);
|
||||
EXPECT_EQ(domain.elements()[0].sourceId.source_label, 1);
|
||||
EXPECT_EQ(domain.elements()[1].sourceId.source_label, 1);
|
||||
EXPECT_EQ(domain.elements()[0].sourceId.instance_name, "Instance-A");
|
||||
EXPECT_EQ(domain.elements()[1].sourceId.instance_name, "Instance-B");
|
||||
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U);
|
||||
EXPECT_EQ(domain.elements()[1].nodeIndices[0], 2U);
|
||||
ASSERT_EQ(domain.Elements().size(), 2U);
|
||||
EXPECT_EQ(domain.Elements()[0].source_id.source_label, 1);
|
||||
EXPECT_EQ(domain.Elements()[1].source_id.source_label, 1);
|
||||
EXPECT_EQ(domain.Elements()[0].source_id.instance_name, "Instance-A");
|
||||
EXPECT_EQ(domain.Elements()[1].source_id.instance_name, "Instance-B");
|
||||
EXPECT_EQ(domain.Elements()[0].node_indices[0], 0U);
|
||||
EXPECT_EQ(domain.Elements()[1].node_indices[0], 2U);
|
||||
}
|
||||
|
||||
// MITC4-MODEL-002
|
||||
TEST(DomainModel, ShellOwnershipPreservesResolvedAssignmentsAndOptionalFrames) {
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = "models/shell-owned.inp";
|
||||
definition.sourceContentIdentity = "fnv1a64:1234567890abcdef";
|
||||
definition.materials = {
|
||||
{"Material-B", 70.0e9, 0.33, {"models/shell-owned.inp", 31U}},
|
||||
{"Material-A", 210.0e9, 0.3, {"models/shell-owned.inp", 30U}}};
|
||||
definition.shellSections = {
|
||||
{"Section-B", 0.02, 0U, {"models/shell-owned.inp", 41U}},
|
||||
{"Section-A", 0.01, 1U, {"models/shell-owned.inp", 40U}}};
|
||||
definition.shellElements = {
|
||||
{{"Shell-Instance", 20, "0020"},
|
||||
fesa::ShellSourceElementType::s4r,
|
||||
{4U, 5U, 6U, 7U},
|
||||
0U,
|
||||
0U,
|
||||
{"models/shell-owned.inp", 21U}},
|
||||
{{"Shell-Instance", 10, "0010"},
|
||||
fesa::ShellSourceElementType::s4,
|
||||
{0U, 1U, 2U, 3U},
|
||||
1U,
|
||||
1U,
|
||||
{"models/shell-owned.inp", 20U}}};
|
||||
definition.shellNodeInitialFrames = {
|
||||
{4U, {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}},
|
||||
{0U, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, -1.0}}};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.source_path = "models/shell-owned.inp";
|
||||
definition.source_content_identity = "fnv1a64:1234567890abcdef";
|
||||
definition.materials = {
|
||||
{"Material-B", 70.0e9, 0.33, {"models/shell-owned.inp", 31U}},
|
||||
{"Material-A", 210.0e9, 0.3, {"models/shell-owned.inp", 30U}}};
|
||||
definition.shell_sections = {
|
||||
{"Section-B", 0.02, 0U, {"models/shell-owned.inp", 41U}},
|
||||
{"Section-A", 0.01, 1U, {"models/shell-owned.inp", 40U}}};
|
||||
definition.shell_elements = {{{"Shell-Instance", 20, "0020"},
|
||||
fesa::ShellSourceElementType::kS4r,
|
||||
{4U, 5U, 6U, 7U},
|
||||
0U,
|
||||
0U,
|
||||
{"models/shell-owned.inp", 21U}},
|
||||
{{"Shell-Instance", 10, "0010"},
|
||||
fesa::ShellSourceElementType::kS4,
|
||||
{0U, 1U, 2U, 3U},
|
||||
1U,
|
||||
1U,
|
||||
{"models/shell-owned.inp", 20U}}};
|
||||
definition.shell_node_initial_frames = {
|
||||
{4U, {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}},
|
||||
{0U, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, -1.0}}};
|
||||
|
||||
auto result = fesa::Domain::create(definition);
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
auto result = fesa::Domain::Create(definition);
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
|
||||
definition.shellSections[0].thickness = -1.0;
|
||||
definition.shellElements[0].sourceId.source_label_text = "mutated";
|
||||
definition.shellNodeInitialFrames[0].director[2] = -1.0;
|
||||
definition.shell_sections[0].thickness = -1.0;
|
||||
definition.shell_elements[0].source_id.source_label_text = "mutated";
|
||||
definition.shell_node_initial_frames[0].director[2] = -1.0;
|
||||
|
||||
const fesa::Domain& domain = result.Value();
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const fesa::Domain&>().shellElements()),
|
||||
const std::vector<fesa::Mitc4ShellDefinition>&>);
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const fesa::Domain&>().shellSections()),
|
||||
const std::vector<fesa::ShellSection>&>);
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const fesa::Domain&>().shellNodeInitialFrames()),
|
||||
const std::vector<fesa::ShellNodeInitialFrame>&>);
|
||||
const fesa::Domain& domain = result.Value();
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const fesa::Domain&>().ShellElements()),
|
||||
const std::vector<fesa::Mitc4ShellDefinition>&>);
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const fesa::Domain&>().ShellSections()),
|
||||
const std::vector<fesa::ShellSection>&>);
|
||||
static_assert(
|
||||
std::is_same_v<decltype(std::declval<const fesa::Domain&>()
|
||||
.ShellNodeInitialFrames()),
|
||||
const std::vector<fesa::ShellNodeInitialFrame>&>);
|
||||
|
||||
ASSERT_EQ(domain.shellElements().size(), 2U);
|
||||
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label, 20);
|
||||
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label_text, "0020");
|
||||
EXPECT_EQ(domain.shellElements()[1].sourceId.source_label, 10);
|
||||
EXPECT_EQ(domain.shellElements()[0].sourceType, fesa::ShellSourceElementType::s4r);
|
||||
EXPECT_EQ(domain.shellElements()[1].sourceType, fesa::ShellSourceElementType::s4);
|
||||
EXPECT_EQ(domain.shellElements()[0].nodeIndices[3], 7U);
|
||||
EXPECT_EQ(domain.shellElements()[0].materialIndex, 0U);
|
||||
EXPECT_EQ(domain.shellElements()[0].sectionIndex, 0U);
|
||||
ASSERT_EQ(domain.ShellElements().size(), 2U);
|
||||
EXPECT_EQ(domain.ShellElements()[0].source_id.source_label, 20);
|
||||
EXPECT_EQ(domain.ShellElements()[0].source_id.source_label_text, "0020");
|
||||
EXPECT_EQ(domain.ShellElements()[1].source_id.source_label, 10);
|
||||
EXPECT_EQ(domain.ShellElements()[0].source_type,
|
||||
fesa::ShellSourceElementType::kS4r);
|
||||
EXPECT_EQ(domain.ShellElements()[1].source_type,
|
||||
fesa::ShellSourceElementType::kS4);
|
||||
EXPECT_EQ(domain.ShellElements()[0].node_indices[3], 7U);
|
||||
EXPECT_EQ(domain.ShellElements()[0].material_index, 0U);
|
||||
EXPECT_EQ(domain.ShellElements()[0].section_index, 0U);
|
||||
|
||||
ASSERT_EQ(domain.shellSections().size(), 2U);
|
||||
EXPECT_EQ(domain.shellSections()[0].name, "Section-B");
|
||||
EXPECT_DOUBLE_EQ(domain.shellSections()[0].thickness, 0.02);
|
||||
EXPECT_EQ(domain.shellSections()[0].materialIndex, 0U);
|
||||
EXPECT_EQ(domain.shellSections()[1].name, "Section-A");
|
||||
ASSERT_EQ(domain.ShellSections().size(), 2U);
|
||||
EXPECT_EQ(domain.ShellSections()[0].name, "Section-B");
|
||||
EXPECT_DOUBLE_EQ(domain.ShellSections()[0].thickness, 0.02);
|
||||
EXPECT_EQ(domain.ShellSections()[0].material_index, 0U);
|
||||
EXPECT_EQ(domain.ShellSections()[1].name, "Section-A");
|
||||
|
||||
ASSERT_EQ(domain.shellNodeInitialFrames().size(), 2U);
|
||||
EXPECT_EQ(domain.shellNodeInitialFrames()[0].nodeIndex, 4U);
|
||||
EXPECT_EQ(
|
||||
domain.shellNodeInitialFrames()[0].director,
|
||||
(std::array<double, 3>{0.0, 0.0, 1.0}));
|
||||
EXPECT_EQ(
|
||||
domain.shellNodeInitialFrames()[0].tangentA,
|
||||
(std::array<double, 3>{1.0, 0.0, 0.0}));
|
||||
EXPECT_EQ(
|
||||
domain.shellNodeInitialFrames()[0].tangentB,
|
||||
(std::array<double, 3>{0.0, 1.0, 0.0}));
|
||||
ASSERT_EQ(domain.ShellNodeInitialFrames().size(), 2U);
|
||||
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].node_index, 4U);
|
||||
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].director,
|
||||
(std::array<double, 3>{0.0, 0.0, 1.0}));
|
||||
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].tangent_a,
|
||||
(std::array<double, 3>{1.0, 0.0, 0.0}));
|
||||
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].tangent_b,
|
||||
(std::array<double, 3>{0.0, 1.0, 0.0}));
|
||||
|
||||
auto noFramesResult = fesa::Domain::create(fesa::ModelDefinition{});
|
||||
ASSERT_TRUE(noFramesResult.HasValue());
|
||||
EXPECT_TRUE(noFramesResult.Value().shellNodeInitialFrames().empty());
|
||||
auto no_frames_result = fesa::Domain::Create(fesa::ModelDefinition{});
|
||||
ASSERT_TRUE(no_frames_result.HasValue());
|
||||
EXPECT_TRUE(no_frames_result.Value().ShellNodeInitialFrames().empty());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "fesa/model/model_types.hpp"
|
||||
#include "fesa/model/model_types.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -14,174 +14,150 @@
|
||||
|
||||
namespace {
|
||||
|
||||
template<class T, class = void>
|
||||
template <class T, class = void>
|
||||
struct HasEquationId : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
struct HasEquationId<T, std::void_t<decltype(std::declval<T&>().equationId)>>
|
||||
: std::true_type {};
|
||||
|
||||
template<class T, class = void>
|
||||
template <class T, class = void>
|
||||
struct HasEquationIds : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
struct HasEquationIds<T, std::void_t<decltype(std::declval<T&>().equationIds)>>
|
||||
: std::true_type {};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
|
||||
const fesa::SourceLocation nodeLocation{
|
||||
std::filesystem::path{"models/beam.inp"}, 11U};
|
||||
const fesa::SourceEntityId firstIdentity{"Instance-A", 7, "0007"};
|
||||
const fesa::SourceEntityId equalIdentity{"Instance-A", 7, "0007"};
|
||||
const fesa::SourceEntityId laterIdentity{"Instance-B", 7, "0007"};
|
||||
const fesa::SourceLocation node_location{
|
||||
std::filesystem::path{"models/beam.inp"}, 11U};
|
||||
const fesa::SourceEntityId first_identity{"Instance-A", 7, "0007"};
|
||||
const fesa::SourceEntityId equal_identity{"Instance-A", 7, "0007"};
|
||||
const fesa::SourceEntityId later_identity{"Instance-B", 7, "0007"};
|
||||
|
||||
EXPECT_EQ(
|
||||
std::make_tuple(
|
||||
firstIdentity.instance_name,
|
||||
firstIdentity.source_label,
|
||||
firstIdentity.source_label_text),
|
||||
std::make_tuple(
|
||||
equalIdentity.instance_name,
|
||||
equalIdentity.source_label,
|
||||
equalIdentity.source_label_text));
|
||||
EXPECT_LT(
|
||||
std::make_tuple(
|
||||
firstIdentity.instance_name,
|
||||
firstIdentity.source_label,
|
||||
firstIdentity.source_label_text),
|
||||
std::make_tuple(
|
||||
laterIdentity.instance_name,
|
||||
laterIdentity.source_label,
|
||||
laterIdentity.source_label_text));
|
||||
EXPECT_EQ(
|
||||
std::make_tuple(first_identity.instance_name, first_identity.source_label,
|
||||
first_identity.source_label_text),
|
||||
std::make_tuple(equal_identity.instance_name, equal_identity.source_label,
|
||||
equal_identity.source_label_text));
|
||||
EXPECT_LT(
|
||||
std::make_tuple(first_identity.instance_name, first_identity.source_label,
|
||||
first_identity.source_label_text),
|
||||
std::make_tuple(later_identity.instance_name, later_identity.source_label,
|
||||
later_identity.source_label_text));
|
||||
|
||||
const fesa::Node node{firstIdentity, {1.0, 2.0, 3.0}, nodeLocation};
|
||||
const fesa::LinearElasticMaterial material{
|
||||
"Steel", 210.0e9, 0.3, {"models/beam.inp", 30U}};
|
||||
const fesa::GeneralBeamSection section{
|
||||
"Section-1",
|
||||
0.02,
|
||||
1.0e-5,
|
||||
0.0,
|
||||
2.0e-5,
|
||||
5.0e-6,
|
||||
{0.0, 1.0, 0.0},
|
||||
{{-0.1, 0.0}, {0.1, 0.0}},
|
||||
{"models/beam.inp", 40U}};
|
||||
const fesa::EulerBeam3DDefinition element{
|
||||
{"Instance-A", 3, "0003"},
|
||||
{fesa::EntityIndex{5}, fesa::EntityIndex{9}},
|
||||
fesa::EntityIndex{2},
|
||||
fesa::EntityIndex{4},
|
||||
{"models/beam.inp", 20U}};
|
||||
const fesa::BoundaryCondition boundary{
|
||||
"Root", 1, 6, 0.0, {"models/beam.inp", 60U}};
|
||||
const fesa::NodalLoad load{
|
||||
"Tip", 3, -1000.0, {"models/beam.inp", 70U}};
|
||||
const fesa::StaticStepDefinition step{
|
||||
"Step-1",
|
||||
{boundary},
|
||||
{load},
|
||||
0.1,
|
||||
1.0,
|
||||
1.0e-5,
|
||||
1.0,
|
||||
{"models/beam.inp", 50U}};
|
||||
const fesa::NodeSet nodeSet{
|
||||
"Root", std::string{"Instance-A"}, {5U}, {"models/beam.inp", 25U}};
|
||||
const fesa::ElementSet elementSet{
|
||||
"Beam", std::string{"Instance-A"}, {4U}, {"models/beam.inp", 26U}};
|
||||
const fesa::PartDefinition part{
|
||||
"BeamPart", {7, 8}, {3}, {"Root", "Tip"}, {"Beam"},
|
||||
{"models/beam.inp", 2U}};
|
||||
const fesa::InstanceDefinition instance{
|
||||
"Instance-A",
|
||||
"BeamPart",
|
||||
{{7, 5U}, {8, 9U}},
|
||||
{{3, 4U}},
|
||||
{"models/beam.inp", 80U}};
|
||||
const fesa::Node node{first_identity, {1.0, 2.0, 3.0}, node_location};
|
||||
const fesa::LinearElasticMaterial material{
|
||||
"Steel", 210.0e9, 0.3, {"models/beam.inp", 30U}};
|
||||
const fesa::GeneralBeamSection section{"Section-1",
|
||||
0.02,
|
||||
1.0e-5,
|
||||
0.0,
|
||||
2.0e-5,
|
||||
5.0e-6,
|
||||
{0.0, 1.0, 0.0},
|
||||
{{-0.1, 0.0}, {0.1, 0.0}},
|
||||
{"models/beam.inp", 40U}};
|
||||
const fesa::EulerBeam3DDefinition element{
|
||||
{"Instance-A", 3, "0003"},
|
||||
{fesa::EntityIndex{5}, fesa::EntityIndex{9}},
|
||||
fesa::EntityIndex{2},
|
||||
fesa::EntityIndex{4},
|
||||
{"models/beam.inp", 20U}};
|
||||
const fesa::BoundaryCondition boundary{
|
||||
"Root", 1, 6, 0.0, {"models/beam.inp", 60U}};
|
||||
const fesa::NodalLoad load{"Tip", 3, -1000.0, {"models/beam.inp", 70U}};
|
||||
const fesa::StaticStepDefinition step{
|
||||
"Step-1", {boundary}, {load}, 0.1,
|
||||
1.0, 1.0e-5, 1.0, {"models/beam.inp", 50U}};
|
||||
const fesa::NodeSet node_set{
|
||||
"Root", std::string{"Instance-A"}, {5U}, {"models/beam.inp", 25U}};
|
||||
const fesa::ElementSet element_set{
|
||||
"Beam", std::string{"Instance-A"}, {4U}, {"models/beam.inp", 26U}};
|
||||
const fesa::PartDefinition part{"BeamPart", {7, 8},
|
||||
{3}, {"Root", "Tip"},
|
||||
{"Beam"}, {"models/beam.inp", 2U}};
|
||||
const fesa::InstanceDefinition instance{"Instance-A",
|
||||
"BeamPart",
|
||||
{{7, 5U}, {8, 9U}},
|
||||
{{3, 4U}},
|
||||
{"models/beam.inp", 80U}};
|
||||
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = "models/beam.inp";
|
||||
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
|
||||
definition.heading = "Identity mapping fixture";
|
||||
definition.nodes = {node};
|
||||
definition.elements = {element};
|
||||
definition.materials = {material};
|
||||
definition.sections = {section};
|
||||
definition.nodeSets = {nodeSet};
|
||||
definition.elementSets = {elementSet};
|
||||
definition.parts = {part};
|
||||
definition.instances = {instance};
|
||||
definition.steps = {step};
|
||||
definition.warnings = {{
|
||||
fesa::Severity::kWarning,
|
||||
"ignored-output-request",
|
||||
{"models/beam.inp", 75U},
|
||||
"*OUTPUT",
|
||||
"",
|
||||
"Output request does not alter mandatory FESA results."}};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.source_path = "models/beam.inp";
|
||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||
definition.heading = "Identity mapping fixture";
|
||||
definition.nodes = {node};
|
||||
definition.elements = {element};
|
||||
definition.materials = {material};
|
||||
definition.sections = {section};
|
||||
definition.node_sets = {node_set};
|
||||
definition.element_sets = {element_set};
|
||||
definition.parts = {part};
|
||||
definition.instances = {instance};
|
||||
definition.steps = {step};
|
||||
definition.warnings = {
|
||||
{fesa::Severity::kWarning,
|
||||
"ignored-output-request",
|
||||
{"models/beam.inp", 75U},
|
||||
"*OUTPUT",
|
||||
"",
|
||||
"Output request does not alter mandatory FESA results."}};
|
||||
|
||||
ASSERT_EQ(definition.nodes.size(), 1U);
|
||||
EXPECT_EQ(definition.nodes[0].sourceId.source_label_text, "0007");
|
||||
EXPECT_EQ(definition.nodes[0].location.line, 11U);
|
||||
ASSERT_EQ(definition.instances.size(), 1U);
|
||||
EXPECT_EQ(definition.instances[0].partName, "BeamPart");
|
||||
EXPECT_EQ(definition.instances[0].nodeMappings[0].sourceLabel, 7);
|
||||
EXPECT_EQ(definition.instances[0].nodeMappings[0].internalIndex, 5U);
|
||||
EXPECT_EQ(definition.instances[0].elementMappings[0].internalIndex, 4U);
|
||||
EXPECT_EQ(definition.steps[0].initialIncrement, 0.1);
|
||||
EXPECT_EQ(definition.steps[0].timePeriod, 1.0);
|
||||
EXPECT_EQ(definition.steps[0].minimumIncrement, 1.0e-5);
|
||||
EXPECT_EQ(definition.steps[0].maximumIncrement, 1.0);
|
||||
ASSERT_EQ(definition.warnings.size(), 1U);
|
||||
EXPECT_EQ(definition.warnings[0].location.line, 75U);
|
||||
ASSERT_EQ(definition.nodes.size(), 1U);
|
||||
EXPECT_EQ(definition.nodes[0].source_id.source_label_text, "0007");
|
||||
EXPECT_EQ(definition.nodes[0].location.line, 11U);
|
||||
ASSERT_EQ(definition.instances.size(), 1U);
|
||||
EXPECT_EQ(definition.instances[0].part_name, "BeamPart");
|
||||
EXPECT_EQ(definition.instances[0].node_mappings[0].source_label, 7);
|
||||
EXPECT_EQ(definition.instances[0].node_mappings[0].internal_index, 5U);
|
||||
EXPECT_EQ(definition.instances[0].element_mappings[0].internal_index, 4U);
|
||||
EXPECT_EQ(definition.steps[0].initial_increment, 0.1);
|
||||
EXPECT_EQ(definition.steps[0].time_period, 1.0);
|
||||
EXPECT_EQ(definition.steps[0].minimum_increment, 1.0e-5);
|
||||
EXPECT_EQ(definition.steps[0].maximum_increment, 1.0);
|
||||
ASSERT_EQ(definition.warnings.size(), 1U);
|
||||
EXPECT_EQ(definition.warnings[0].location.line, 75U);
|
||||
|
||||
static_assert(!HasEquationId<fesa::Node>::value);
|
||||
static_assert(!HasEquationIds<fesa::Node>::value);
|
||||
static_assert(!HasEquationId<fesa::EulerBeam3DDefinition>::value);
|
||||
static_assert(!HasEquationIds<fesa::EulerBeam3DDefinition>::value);
|
||||
static_assert(!HasEquationId<fesa::Node>::value);
|
||||
static_assert(!HasEquationIds<fesa::Node>::value);
|
||||
static_assert(!HasEquationId<fesa::EulerBeam3DDefinition>::value);
|
||||
static_assert(!HasEquationIds<fesa::EulerBeam3DDefinition>::value);
|
||||
}
|
||||
|
||||
// MITC4-MODEL-001
|
||||
TEST(DomainModel, Mitc4ShellRecordsPreserveSourceAndInternalIdentity) {
|
||||
const fesa::Mitc4ShellDefinition s4Element{
|
||||
{"Shell-Instance", 41, "0041"},
|
||||
fesa::ShellSourceElementType::s4,
|
||||
{fesa::EntityIndex{3},
|
||||
fesa::EntityIndex{5},
|
||||
fesa::EntityIndex{8},
|
||||
fesa::EntityIndex{13}},
|
||||
fesa::EntityIndex{2},
|
||||
fesa::EntityIndex{7},
|
||||
{"models/shell.inp", 20U}};
|
||||
const fesa::Mitc4ShellDefinition s4rElement{
|
||||
{"Shell-Instance", 42, "0042"},
|
||||
fesa::ShellSourceElementType::s4r,
|
||||
{fesa::EntityIndex{13},
|
||||
fesa::EntityIndex{8},
|
||||
fesa::EntityIndex{5},
|
||||
fesa::EntityIndex{3}},
|
||||
fesa::EntityIndex{2},
|
||||
fesa::EntityIndex{7},
|
||||
{"models/shell.inp", 21U}};
|
||||
const fesa::Mitc4ShellDefinition s4_element{
|
||||
{"Shell-Instance", 41, "0041"},
|
||||
fesa::ShellSourceElementType::kS4,
|
||||
{fesa::EntityIndex{3}, fesa::EntityIndex{5}, fesa::EntityIndex{8},
|
||||
fesa::EntityIndex{13}},
|
||||
fesa::EntityIndex{2},
|
||||
fesa::EntityIndex{7},
|
||||
{"models/shell.inp", 20U}};
|
||||
const fesa::Mitc4ShellDefinition s4r_element{
|
||||
{"Shell-Instance", 42, "0042"},
|
||||
fesa::ShellSourceElementType::kS4r,
|
||||
{fesa::EntityIndex{13}, fesa::EntityIndex{8}, fesa::EntityIndex{5},
|
||||
fesa::EntityIndex{3}},
|
||||
fesa::EntityIndex{2},
|
||||
fesa::EntityIndex{7},
|
||||
{"models/shell.inp", 21U}};
|
||||
|
||||
EXPECT_EQ(s4Element.sourceId.instance_name, "Shell-Instance");
|
||||
EXPECT_EQ(s4Element.sourceId.source_label, 41);
|
||||
EXPECT_EQ(s4Element.sourceId.source_label_text, "0041");
|
||||
EXPECT_EQ(s4Element.sourceType, fesa::ShellSourceElementType::s4);
|
||||
EXPECT_EQ(s4rElement.sourceType, fesa::ShellSourceElementType::s4r);
|
||||
EXPECT_NE(s4Element.sourceType, s4rElement.sourceType);
|
||||
EXPECT_EQ(
|
||||
fesa::kMitc4InternalFormulation,
|
||||
std::string_view{"FESA-MITC4"});
|
||||
EXPECT_EQ(
|
||||
s4Element.nodeIndices,
|
||||
(std::array<fesa::EntityIndex, 4>{3U, 5U, 8U, 13U}));
|
||||
EXPECT_EQ(s4Element.materialIndex, 2U);
|
||||
EXPECT_EQ(s4Element.sectionIndex, 7U);
|
||||
EXPECT_EQ(s4_element.source_id.instance_name, "Shell-Instance");
|
||||
EXPECT_EQ(s4_element.source_id.source_label, 41);
|
||||
EXPECT_EQ(s4_element.source_id.source_label_text, "0041");
|
||||
EXPECT_EQ(s4_element.source_type, fesa::ShellSourceElementType::kS4);
|
||||
EXPECT_EQ(s4r_element.source_type, fesa::ShellSourceElementType::kS4r);
|
||||
EXPECT_NE(s4_element.source_type, s4r_element.source_type);
|
||||
EXPECT_EQ(fesa::kMitc4InternalFormulation, std::string_view{"FESA-MITC4"});
|
||||
EXPECT_EQ(s4_element.node_indices,
|
||||
(std::array<fesa::EntityIndex, 4>{3U, 5U, 8U, 13U}));
|
||||
EXPECT_EQ(s4_element.material_index, 2U);
|
||||
EXPECT_EQ(s4_element.section_index, 7U);
|
||||
|
||||
static_assert(!HasEquationId<fesa::Mitc4ShellDefinition>::value);
|
||||
static_assert(!HasEquationIds<fesa::Mitc4ShellDefinition>::value);
|
||||
static_assert(!HasEquationId<fesa::Mitc4ShellDefinition>::value);
|
||||
static_assert(!HasEquationIds<fesa::Mitc4ShellDefinition>::value);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "fesa/model/shell_geometry.hpp"
|
||||
#include "fesa/model/shell_geometry.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -14,263 +14,242 @@ namespace {
|
||||
|
||||
using Vector3 = std::array<double, 3>;
|
||||
|
||||
fesa::Node node(fesa::EntityIndex label, Vector3 coordinates) {
|
||||
return {
|
||||
{"Shell-Instance", static_cast<std::int64_t>(label),
|
||||
std::to_string(label)},
|
||||
coordinates,
|
||||
{"shell-geometry.inp", static_cast<std::size_t>(label + 1U)}};
|
||||
fesa::Node Node(fesa::EntityIndex label, Vector3 coordinates) {
|
||||
return {{"Shell-Instance", static_cast<std::int64_t>(label),
|
||||
std::to_string(label)},
|
||||
coordinates,
|
||||
{"shell-geometry.inp", static_cast<std::size_t>(label + 1U)}};
|
||||
}
|
||||
|
||||
fesa::Mitc4ShellDefinition element(
|
||||
fesa::EntityIndex label,
|
||||
std::array<fesa::EntityIndex, 4> nodeIndices) {
|
||||
return {
|
||||
{"Shell-Instance", static_cast<std::int64_t>(label),
|
||||
std::to_string(label)},
|
||||
fesa::ShellSourceElementType::s4,
|
||||
nodeIndices,
|
||||
0U,
|
||||
0U,
|
||||
{"shell-geometry.inp", static_cast<std::size_t>(100U + label)}};
|
||||
fesa::Mitc4ShellDefinition Element(
|
||||
fesa::EntityIndex label, std::array<fesa::EntityIndex, 4> node_indices) {
|
||||
return {{"Shell-Instance", static_cast<std::int64_t>(label),
|
||||
std::to_string(label)},
|
||||
fesa::ShellSourceElementType::kS4,
|
||||
node_indices,
|
||||
0U,
|
||||
0U,
|
||||
{"shell-geometry.inp", static_cast<std::size_t>(100U + label)}};
|
||||
}
|
||||
|
||||
std::vector<fesa::ShellSection> sections(double thickness = 0.2) {
|
||||
return {{"Section", thickness, 0U, {"shell-geometry.inp", 90U}}};
|
||||
std::vector<fesa::ShellSection> Sections(double thickness = 0.2) {
|
||||
return {{"Section", thickness, 0U, {"shell-geometry.inp", 90U}}};
|
||||
}
|
||||
|
||||
double dot(const Vector3& left, const Vector3& right) {
|
||||
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
|
||||
double Dot(const Vector3& left, const Vector3& right) {
|
||||
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
|
||||
}
|
||||
|
||||
Vector3 cross(const Vector3& left, const Vector3& right) {
|
||||
return {
|
||||
left[1] * right[2] - left[2] * right[1],
|
||||
left[2] * right[0] - left[0] * right[2],
|
||||
left[0] * right[1] - left[1] * right[0]};
|
||||
Vector3 Cross(const Vector3& left, const Vector3& right) {
|
||||
return {left[1] * right[2] - left[2] * right[1],
|
||||
left[2] * right[0] - left[0] * right[2],
|
||||
left[0] * right[1] - left[1] * right[0]};
|
||||
}
|
||||
|
||||
double norm(const Vector3& value) {
|
||||
return std::sqrt(dot(value, value));
|
||||
double Norm(const Vector3& value) { return std::sqrt(Dot(value, value)); }
|
||||
|
||||
void ExpectVectorNear(const Vector3& actual, const Vector3& expected,
|
||||
double tolerance = 1.0e-12) {
|
||||
for (std::size_t component = 0U; component < actual.size(); ++component) {
|
||||
EXPECT_NEAR(actual[component], expected[component], tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
void expectVectorNear(
|
||||
const Vector3& actual,
|
||||
const Vector3& expected,
|
||||
double tolerance = 1.0e-12) {
|
||||
for (std::size_t component = 0U; component < actual.size(); ++component) {
|
||||
EXPECT_NEAR(actual[component], expected[component], tolerance);
|
||||
}
|
||||
void ExpectRightHandedFrame(const fesa::ShellNodeInitialFrame& frame) {
|
||||
EXPECT_NEAR(Norm(frame.director), 1.0, 1.0e-12);
|
||||
EXPECT_NEAR(Norm(frame.tangent_a), 1.0, 1.0e-12);
|
||||
EXPECT_NEAR(Norm(frame.tangent_b), 1.0, 1.0e-12);
|
||||
EXPECT_NEAR(Dot(frame.director, frame.tangent_a), 0.0, 1.0e-12);
|
||||
EXPECT_NEAR(Dot(frame.director, frame.tangent_b), 0.0, 1.0e-12);
|
||||
EXPECT_NEAR(Dot(frame.tangent_a, frame.tangent_b), 0.0, 1.0e-12);
|
||||
ExpectVectorNear(Cross(frame.tangent_a, frame.tangent_b), frame.director);
|
||||
}
|
||||
|
||||
void expectRightHandedFrame(const fesa::ShellNodeInitialFrame& frame) {
|
||||
EXPECT_NEAR(norm(frame.director), 1.0, 1.0e-12);
|
||||
EXPECT_NEAR(norm(frame.tangentA), 1.0, 1.0e-12);
|
||||
EXPECT_NEAR(norm(frame.tangentB), 1.0, 1.0e-12);
|
||||
EXPECT_NEAR(dot(frame.director, frame.tangentA), 0.0, 1.0e-12);
|
||||
EXPECT_NEAR(dot(frame.director, frame.tangentB), 0.0, 1.0e-12);
|
||||
EXPECT_NEAR(dot(frame.tangentA, frame.tangentB), 0.0, 1.0e-12);
|
||||
expectVectorNear(cross(frame.tangentA, frame.tangentB), frame.director);
|
||||
const fesa::ShellNodeInitialFrame& FrameFor(const fesa::ShellGeometry& geometry,
|
||||
fesa::EntityIndex node_index) {
|
||||
const auto found =
|
||||
std::find_if(geometry.nodal_frames.begin(), geometry.nodal_frames.end(),
|
||||
[node_index](const fesa::ShellNodeInitialFrame& frame) {
|
||||
return frame.node_index == node_index;
|
||||
});
|
||||
EXPECT_NE(found, geometry.nodal_frames.end());
|
||||
return *found;
|
||||
}
|
||||
|
||||
const fesa::ShellNodeInitialFrame& frameFor(
|
||||
const fesa::ShellGeometry& geometry,
|
||||
fesa::EntityIndex nodeIndex) {
|
||||
const auto found = std::find_if(
|
||||
geometry.nodalFrames.begin(),
|
||||
geometry.nodalFrames.end(),
|
||||
[nodeIndex](const fesa::ShellNodeInitialFrame& frame) {
|
||||
return frame.nodeIndex == nodeIndex;
|
||||
});
|
||||
EXPECT_NE(found, geometry.nodalFrames.end());
|
||||
return *found;
|
||||
void ExpectFailureCode(const fesa::Result<fesa::ShellGeometry>& result,
|
||||
const std::string& code) {
|
||||
ASSERT_FALSE(result.HasValue());
|
||||
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
|
||||
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(result.GetStatus().Diagnostics()[0].code, code);
|
||||
}
|
||||
|
||||
void expectFailureCode(
|
||||
const fesa::Result<fesa::ShellGeometry>& result,
|
||||
const std::string& code) {
|
||||
ASSERT_FALSE(result.HasValue());
|
||||
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
|
||||
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(result.GetStatus().Diagnostics()[0].code, code);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
// MITC4-GEO-001
|
||||
TEST(Mitc4Geometry, BuildsDeterministicFramesForPlanarRotatedAndWarpedElements) {
|
||||
const std::vector<fesa::Node> planarNodes{
|
||||
node(0U, {0.0, 0.0, 0.0}),
|
||||
node(1U, {1.0, 0.0, 0.0}),
|
||||
node(2U, {1.0, 1.0, 0.0}),
|
||||
node(3U, {0.0, 1.0, 0.0})};
|
||||
auto planar = fesa::preprocessShellGeometry(
|
||||
planarNodes, {element(10U, {0U, 1U, 2U, 3U})}, sections());
|
||||
TEST(Mitc4Geometry,
|
||||
BuildsDeterministicFramesForPlanarRotatedAndWarpedElements) {
|
||||
const std::vector<fesa::Node> planar_nodes{
|
||||
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
|
||||
Node(2U, {1.0, 1.0, 0.0}), Node(3U, {0.0, 1.0, 0.0})};
|
||||
auto planar = fesa::PreprocessShellGeometry(
|
||||
planar_nodes, {Element(10U, {0U, 1U, 2U, 3U})}, Sections());
|
||||
|
||||
ASSERT_TRUE(planar.HasValue());
|
||||
ASSERT_EQ(planar.Value().elementData.size(), 1U);
|
||||
expectVectorNear(planar.Value().elementData[0].normalCandidate, {0.0, 0.0, 1.0});
|
||||
EXPECT_NEAR(planar.Value().elementData[0].surfaceAreaWeight, 1.0, 1.0e-12);
|
||||
ASSERT_EQ(planar.Value().nodalFrames.size(), 4U);
|
||||
for (const auto& frame : planar.Value().nodalFrames) {
|
||||
expectVectorNear(frame.director, {0.0, 0.0, 1.0});
|
||||
expectVectorNear(frame.tangentA, {1.0, 0.0, 0.0});
|
||||
expectVectorNear(frame.tangentB, {0.0, 1.0, 0.0});
|
||||
expectRightHandedFrame(frame);
|
||||
}
|
||||
ASSERT_TRUE(planar.HasValue());
|
||||
ASSERT_EQ(planar.Value().element_data.size(), 1U);
|
||||
ExpectVectorNear(planar.Value().element_data[0].normal_candidate,
|
||||
{0.0, 0.0, 1.0});
|
||||
EXPECT_NEAR(planar.Value().element_data[0].surface_area_weight, 1.0, 1.0e-12);
|
||||
ASSERT_EQ(planar.Value().nodal_frames.size(), 4U);
|
||||
for (const auto& frame : planar.Value().nodal_frames) {
|
||||
ExpectVectorNear(frame.director, {0.0, 0.0, 1.0});
|
||||
ExpectVectorNear(frame.tangent_a, {1.0, 0.0, 0.0});
|
||||
ExpectVectorNear(frame.tangent_b, {0.0, 1.0, 0.0});
|
||||
ExpectRightHandedFrame(frame);
|
||||
}
|
||||
|
||||
const std::vector<fesa::Node> rotatedNodes{
|
||||
node(0U, {0.0, 0.0, 0.0}),
|
||||
node(1U, {0.0, 1.0, 0.0}),
|
||||
node(2U, {0.0, 1.0, 1.0}),
|
||||
node(3U, {0.0, 0.0, 1.0})};
|
||||
auto rotated = fesa::preprocessShellGeometry(
|
||||
rotatedNodes, {element(11U, {0U, 1U, 2U, 3U})}, sections());
|
||||
const std::vector<fesa::Node> rotated_nodes{
|
||||
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {0.0, 1.0, 0.0}),
|
||||
Node(2U, {0.0, 1.0, 1.0}), Node(3U, {0.0, 0.0, 1.0})};
|
||||
auto rotated = fesa::PreprocessShellGeometry(
|
||||
rotated_nodes, {Element(11U, {0U, 1U, 2U, 3U})}, Sections());
|
||||
|
||||
ASSERT_TRUE(rotated.HasValue());
|
||||
const auto& rotatedFrame = frameFor(rotated.Value(), 0U);
|
||||
expectVectorNear(rotatedFrame.director, {1.0, 0.0, 0.0});
|
||||
expectVectorNear(rotatedFrame.tangentA, {0.0, 1.0, 0.0});
|
||||
expectVectorNear(rotatedFrame.tangentB, {0.0, 0.0, 1.0});
|
||||
expectRightHandedFrame(rotatedFrame);
|
||||
ASSERT_TRUE(rotated.HasValue());
|
||||
const auto& rotated_frame = FrameFor(rotated.Value(), 0U);
|
||||
ExpectVectorNear(rotated_frame.director, {1.0, 0.0, 0.0});
|
||||
ExpectVectorNear(rotated_frame.tangent_a, {0.0, 1.0, 0.0});
|
||||
ExpectVectorNear(rotated_frame.tangent_b, {0.0, 0.0, 1.0});
|
||||
ExpectRightHandedFrame(rotated_frame);
|
||||
|
||||
const std::vector<fesa::Node> warpedNodes{
|
||||
node(0U, {0.0, 0.0, 0.0}),
|
||||
node(1U, {2.0, 0.0, 0.0}),
|
||||
node(2U, {2.0, 1.0, 0.2}),
|
||||
node(3U, {0.0, 1.0, 0.0})};
|
||||
auto warped = fesa::preprocessShellGeometry(
|
||||
warpedNodes, {element(12U, {0U, 1U, 2U, 3U})}, sections());
|
||||
const std::vector<fesa::Node> warped_nodes{
|
||||
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {2.0, 0.0, 0.0}),
|
||||
Node(2U, {2.0, 1.0, 0.2}), Node(3U, {0.0, 1.0, 0.0})};
|
||||
auto warped = fesa::PreprocessShellGeometry(
|
||||
warped_nodes, {Element(12U, {0U, 1U, 2U, 3U})}, Sections());
|
||||
|
||||
ASSERT_TRUE(warped.HasValue());
|
||||
EXPECT_GT(warped.Value().elementData[0].surfaceAreaWeight, 2.0);
|
||||
for (const auto& frame : warped.Value().nodalFrames) {
|
||||
expectRightHandedFrame(frame);
|
||||
}
|
||||
ASSERT_TRUE(warped.HasValue());
|
||||
EXPECT_GT(warped.Value().element_data[0].surface_area_weight, 2.0);
|
||||
for (const auto& frame : warped.Value().nodal_frames) {
|
||||
ExpectRightHandedFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
// MITC4-GEO-002
|
||||
TEST(Mitc4Geometry, AreaWeightsSharedDirectorsInStableSourceIdentityOrder) {
|
||||
const std::vector<fesa::Node> nodes{
|
||||
node(0U, {0.0, 0.0, 0.0}),
|
||||
node(1U, {1.0, 0.0, 0.0}),
|
||||
node(2U, {1.0, 1.0, 0.0}),
|
||||
node(3U, {0.0, 1.0, 0.0}),
|
||||
node(4U, {2.0, 0.0, 1.0}),
|
||||
node(5U, {2.0, 1.0, 1.0})};
|
||||
const auto flat = element(10U, {0U, 1U, 2U, 3U});
|
||||
const auto tilted = element(20U, {1U, 4U, 5U, 2U});
|
||||
const std::vector<fesa::Node> nodes{
|
||||
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
|
||||
Node(2U, {1.0, 1.0, 0.0}), Node(3U, {0.0, 1.0, 0.0}),
|
||||
Node(4U, {2.0, 0.0, 1.0}), Node(5U, {2.0, 1.0, 1.0})};
|
||||
const auto flat = Element(10U, {0U, 1U, 2U, 3U});
|
||||
const auto tilted = Element(20U, {1U, 4U, 5U, 2U});
|
||||
|
||||
auto first = fesa::preprocessShellGeometry(
|
||||
nodes, {tilted, flat}, sections());
|
||||
auto second = fesa::preprocessShellGeometry(
|
||||
nodes, {flat, tilted}, sections());
|
||||
auto first = fesa::PreprocessShellGeometry(nodes, {tilted, flat}, Sections());
|
||||
auto second =
|
||||
fesa::PreprocessShellGeometry(nodes, {flat, tilted}, Sections());
|
||||
|
||||
ASSERT_TRUE(first.HasValue());
|
||||
ASSERT_TRUE(second.HasValue());
|
||||
const Vector3 expectedSharedDirector{
|
||||
-1.0 / std::sqrt(5.0), 0.0, 2.0 / std::sqrt(5.0)};
|
||||
for (const auto sharedNode : {1U, 2U}) {
|
||||
const auto& firstFrame = frameFor(first.Value(), sharedNode);
|
||||
const auto& secondFrame = frameFor(second.Value(), sharedNode);
|
||||
expectVectorNear(firstFrame.director, expectedSharedDirector);
|
||||
expectVectorNear(firstFrame.director, secondFrame.director, 0.0);
|
||||
expectVectorNear(firstFrame.tangentA, {0.0, 1.0, 0.0});
|
||||
expectRightHandedFrame(firstFrame);
|
||||
}
|
||||
ASSERT_TRUE(first.HasValue());
|
||||
ASSERT_TRUE(second.HasValue());
|
||||
const Vector3 expected_shared_director{-1.0 / std::sqrt(5.0), 0.0,
|
||||
2.0 / std::sqrt(5.0)};
|
||||
for (const auto sharedNode : {1U, 2U}) {
|
||||
const auto& firstFrame = FrameFor(first.Value(), sharedNode);
|
||||
const auto& secondFrame = FrameFor(second.Value(), sharedNode);
|
||||
ExpectVectorNear(firstFrame.director, expected_shared_director);
|
||||
ExpectVectorNear(firstFrame.director, secondFrame.director, 0.0);
|
||||
ExpectVectorNear(firstFrame.tangent_a, {0.0, 1.0, 0.0});
|
||||
ExpectRightHandedFrame(firstFrame);
|
||||
}
|
||||
}
|
||||
|
||||
// MITC4-GEO-003
|
||||
TEST(Mitc4Geometry, RejectsInvalidSurfaceJacobianAndIncidentOrientationCases) {
|
||||
const auto validElement = element(10U, {0U, 1U, 2U, 3U});
|
||||
const auto valid_element = Element(10U, {0U, 1U, 2U, 3U});
|
||||
|
||||
expectFailureCode(
|
||||
fesa::preprocessShellGeometry(
|
||||
{node(0U, {0.0, 0.0, 0.0}), node(1U, {0.0, 0.0, 0.0}),
|
||||
node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0})},
|
||||
{validElement}, sections()),
|
||||
"invalid-shell-geometry");
|
||||
ExpectFailureCode(fesa::PreprocessShellGeometry(
|
||||
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {0.0, 0.0, 0.0}),
|
||||
Node(2U, {1.0, 1.0, 0.0}), Node(3U, {0.0, 1.0, 0.0})},
|
||||
{valid_element}, Sections()),
|
||||
"invalid-shell-geometry");
|
||||
|
||||
expectFailureCode(
|
||||
fesa::preprocessShellGeometry(
|
||||
{node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 1.0, 0.0}),
|
||||
node(2U, {0.0, 1.0, 0.0}), node(3U, {1.0, 0.0, 0.0})},
|
||||
{validElement}, sections()),
|
||||
"invalid-shell-geometry");
|
||||
ExpectFailureCode(fesa::PreprocessShellGeometry(
|
||||
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 1.0, 0.0}),
|
||||
Node(2U, {0.0, 1.0, 0.0}), Node(3U, {1.0, 0.0, 0.0})},
|
||||
{valid_element}, Sections()),
|
||||
"invalid-shell-geometry");
|
||||
|
||||
expectFailureCode(
|
||||
fesa::preprocessShellGeometry(
|
||||
{node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}),
|
||||
node(2U, {2.0, 0.0, 0.0}), node(3U, {3.0, 0.0, 0.0})},
|
||||
{validElement}, sections()),
|
||||
"invalid-shell-geometry");
|
||||
ExpectFailureCode(fesa::PreprocessShellGeometry(
|
||||
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
|
||||
Node(2U, {2.0, 0.0, 0.0}), Node(3U, {3.0, 0.0, 0.0})},
|
||||
{valid_element}, Sections()),
|
||||
"invalid-shell-geometry");
|
||||
|
||||
expectFailureCode(
|
||||
fesa::preprocessShellGeometry(
|
||||
{node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}),
|
||||
node(2U, {0.05, 0.05, 0.0}), node(3U, {0.0, 1.0, 0.0})},
|
||||
{validElement}, sections()),
|
||||
"invalid-shell-geometry");
|
||||
ExpectFailureCode(
|
||||
fesa::PreprocessShellGeometry(
|
||||
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
|
||||
Node(2U, {0.05, 0.05, 0.0}), Node(3U, {0.0, 1.0, 0.0})},
|
||||
{valid_element}, Sections()),
|
||||
"invalid-shell-geometry");
|
||||
|
||||
expectFailureCode(
|
||||
fesa::preprocessShellGeometry(
|
||||
{node(0U, {0.0, 0.0, 0.0}),
|
||||
node(1U, {1.0, 0.0, 0.0}),
|
||||
node(2U, {1.0, std::numeric_limits<double>::quiet_NaN(), 0.0}),
|
||||
node(3U, {0.0, 1.0, 0.0})},
|
||||
{validElement}, sections()),
|
||||
"invalid-shell-geometry");
|
||||
ExpectFailureCode(
|
||||
fesa::PreprocessShellGeometry(
|
||||
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
|
||||
Node(2U, {1.0, std::numeric_limits<double>::quiet_NaN(), 0.0}),
|
||||
Node(3U, {0.0, 1.0, 0.0})},
|
||||
{valid_element}, Sections()),
|
||||
"invalid-shell-geometry");
|
||||
|
||||
expectFailureCode(
|
||||
fesa::preprocessShellGeometry(
|
||||
{node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}),
|
||||
node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0})},
|
||||
{validElement}, sections(0.0)),
|
||||
"invalid-shell-jacobian");
|
||||
ExpectFailureCode(fesa::PreprocessShellGeometry(
|
||||
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
|
||||
Node(2U, {1.0, 1.0, 0.0}), Node(3U, {0.0, 1.0, 0.0})},
|
||||
{valid_element}, Sections(0.0)),
|
||||
"invalid-shell-jacobian");
|
||||
|
||||
const std::vector<fesa::Node> opposedNodes{
|
||||
node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}),
|
||||
node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0})};
|
||||
expectFailureCode(
|
||||
fesa::preprocessShellGeometry(
|
||||
opposedNodes,
|
||||
{element(10U, {0U, 1U, 2U, 3U}),
|
||||
element(20U, {0U, 3U, 2U, 1U})},
|
||||
sections()),
|
||||
"opposed-incident-normal");
|
||||
const std::vector<fesa::Node> opposed_nodes{
|
||||
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
|
||||
Node(2U, {1.0, 1.0, 0.0}), Node(3U, {0.0, 1.0, 0.0})};
|
||||
ExpectFailureCode(
|
||||
fesa::PreprocessShellGeometry(
|
||||
opposed_nodes,
|
||||
{Element(10U, {0U, 1U, 2U, 3U}), Element(20U, {0U, 3U, 2U, 1U})},
|
||||
Sections()),
|
||||
"opposed-incident-normal");
|
||||
}
|
||||
|
||||
// MITC4-GEO-004
|
||||
TEST(Mitc4Geometry, ExposesTheCompleteRequiredValidationPointInventory) {
|
||||
const auto& points = fesa::shellGeometryValidationPoints();
|
||||
ASSERT_EQ(points.size(), 17U);
|
||||
EXPECT_EQ(
|
||||
std::count_if(points.begin(), points.end(), [](const auto& point) {
|
||||
return point.kind == fesa::ShellGeometryPointKind::center;
|
||||
}),
|
||||
1);
|
||||
EXPECT_EQ(
|
||||
std::count_if(points.begin(), points.end(), [](const auto& point) {
|
||||
return point.kind == fesa::ShellGeometryPointKind::stiffness;
|
||||
}),
|
||||
8);
|
||||
EXPECT_EQ(
|
||||
std::count_if(points.begin(), points.end(), [](const auto& point) {
|
||||
return point.kind == fesa::ShellGeometryPointKind::tying;
|
||||
}),
|
||||
4);
|
||||
EXPECT_EQ(
|
||||
std::count_if(points.begin(), points.end(), [](const auto& point) {
|
||||
return point.kind == fesa::ShellGeometryPointKind::recovery;
|
||||
}),
|
||||
4);
|
||||
const auto& points = fesa::ShellGeometryValidationPoints();
|
||||
ASSERT_EQ(points.size(), 17U);
|
||||
EXPECT_EQ(std::count_if(points.begin(), points.end(),
|
||||
[](const auto& point) {
|
||||
return point.kind ==
|
||||
fesa::ShellGeometryPointKind::kCenter;
|
||||
}),
|
||||
1);
|
||||
EXPECT_EQ(std::count_if(points.begin(), points.end(),
|
||||
[](const auto& point) {
|
||||
return point.kind ==
|
||||
fesa::ShellGeometryPointKind::kStiffness;
|
||||
}),
|
||||
8);
|
||||
EXPECT_EQ(std::count_if(points.begin(), points.end(),
|
||||
[](const auto& point) {
|
||||
return point.kind ==
|
||||
fesa::ShellGeometryPointKind::kTying;
|
||||
}),
|
||||
4);
|
||||
EXPECT_EQ(std::count_if(points.begin(), points.end(),
|
||||
[](const auto& point) {
|
||||
return point.kind ==
|
||||
fesa::ShellGeometryPointKind::kRecovery;
|
||||
}),
|
||||
4);
|
||||
|
||||
EXPECT_EQ(points.front().naturalCoordinates, (Vector3{0.0, 0.0, 0.0}));
|
||||
const double gauss = 1.0 / std::sqrt(3.0);
|
||||
EXPECT_EQ(points[1].naturalCoordinates, (Vector3{-gauss, -gauss, -gauss}));
|
||||
EXPECT_EQ(points[8].naturalCoordinates, (Vector3{-gauss, gauss, gauss}));
|
||||
EXPECT_EQ(points[9].naturalCoordinates, (Vector3{0.0, -1.0, 0.0}));
|
||||
EXPECT_EQ(points[12].naturalCoordinates, (Vector3{1.0, 0.0, 0.0}));
|
||||
EXPECT_EQ(points[13].naturalCoordinates, (Vector3{-gauss, -gauss, 0.0}));
|
||||
EXPECT_EQ(points[16].naturalCoordinates, (Vector3{-gauss, gauss, 0.0}));
|
||||
EXPECT_EQ(points.front().natural_coordinates, (Vector3{0.0, 0.0, 0.0}));
|
||||
const double gauss = 1.0 / std::sqrt(3.0);
|
||||
EXPECT_EQ(points[1].natural_coordinates, (Vector3{-gauss, -gauss, -gauss}));
|
||||
EXPECT_EQ(points[8].natural_coordinates, (Vector3{-gauss, gauss, gauss}));
|
||||
EXPECT_EQ(points[9].natural_coordinates, (Vector3{0.0, -1.0, 0.0}));
|
||||
EXPECT_EQ(points[12].natural_coordinates, (Vector3{1.0, 0.0, 0.0}));
|
||||
EXPECT_EQ(points[13].natural_coordinates, (Vector3{-gauss, -gauss, 0.0}));
|
||||
EXPECT_EQ(points[16].natural_coordinates, (Vector3{-gauss, gauss, 0.0}));
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "fesa/assembly/parallel_for.hpp"
|
||||
#include "fesa/assembly/sparse_assembler.hpp"
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -51,8 +51,8 @@ fesa::ModelDefinition makeDefinition(
|
||||
const bool nonzeroPrescription = true) {
|
||||
const std::filesystem::path source{"models/result-recovery.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||
definition.nodes = {
|
||||
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||
{{"Beam-1", 2, "2"}, {kLength, 0.0, 0.0}, {source, 11U}}};
|
||||
@@ -110,7 +110,7 @@ RecoveryFixture makeFixture(
|
||||
const bool reverseSecond = false,
|
||||
const bool sectionJump = false,
|
||||
const bool nonzeroPrescription = true) {
|
||||
auto domainResult = fesa::Domain::create(makeDefinition(
|
||||
auto domainResult = fesa::Domain::Create(makeDefinition(
|
||||
twoElements,
|
||||
std::move(sectionPoints),
|
||||
std::move(loads),
|
||||
@@ -158,8 +158,8 @@ fesa::ModelDefinition makeShellDefinition(
|
||||
std::vector<fesa::NodalLoad> loads = {}) {
|
||||
const std::filesystem::path source{"models/shell-result-recovery.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.sourcePath = source;
|
||||
definition.sourceContentIdentity = "fnv1a64:fedcba9876543210";
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:fedcba9876543210";
|
||||
definition.nodes = {
|
||||
{{"Shell-1", 1, "1"}, {-1.0, -1.0, 0.0}, {source, 10U}},
|
||||
{{"Shell-1", 2, "2"}, {1.0, -1.0, 0.0}, {source, 11U}},
|
||||
@@ -175,26 +175,26 @@ fesa::ModelDefinition makeShellDefinition(
|
||||
}
|
||||
definition.materials = {
|
||||
{"Material", 120.0, 0.25, {source, 20U}}};
|
||||
definition.shellSections = {
|
||||
definition.shell_sections = {
|
||||
{"ShellSection", 2.0, 0U, {source, 30U}}};
|
||||
definition.shellElements = {{
|
||||
definition.shell_elements = {{
|
||||
{"Shell-1", 10, "10"},
|
||||
fesa::ShellSourceElementType::s4,
|
||||
fesa::ShellSourceElementType::kS4,
|
||||
{0U, 1U, twoElements ? 4U : 3U, twoElements ? 3U : 2U},
|
||||
0U,
|
||||
0U,
|
||||
{source, 40U}}};
|
||||
if (twoElements) {
|
||||
definition.shellElements.push_back({
|
||||
definition.shell_elements.push_back({
|
||||
{"Shell-1", 20, "20"},
|
||||
fesa::ShellSourceElementType::s4r,
|
||||
fesa::ShellSourceElementType::kS4r,
|
||||
{1U, 2U, 5U, 4U},
|
||||
0U,
|
||||
0U,
|
||||
{source, 41U}});
|
||||
}
|
||||
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
||||
definition.shellNodeInitialFrames.push_back({
|
||||
definition.shell_node_initial_frames.push_back({
|
||||
static_cast<fesa::EntityIndex>(node),
|
||||
{0.0, 0.0, 1.0},
|
||||
{1.0, 0.0, 0.0},
|
||||
@@ -206,7 +206,7 @@ fesa::ModelDefinition makeShellDefinition(
|
||||
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
||||
allNodes.push_back(static_cast<fesa::EntityIndex>(node));
|
||||
}
|
||||
definition.nodeSets.push_back(
|
||||
definition.node_sets.push_back(
|
||||
{"All", {}, std::move(allNodes), {source, 50U}});
|
||||
}
|
||||
definition.steps = {{
|
||||
@@ -225,7 +225,7 @@ fesa::ModelDefinition makeShellDefinition(
|
||||
}
|
||||
|
||||
ShellRecoveryFixture makeShellFixture(fesa::ModelDefinition definition) {
|
||||
auto domainResult = fesa::Domain::create(std::move(definition));
|
||||
auto domainResult = fesa::Domain::Create(std::move(definition));
|
||||
if (!domainResult.HasValue()) {
|
||||
throw std::runtime_error{
|
||||
"Shell recovery fixture Domain construction failed."};
|
||||
@@ -272,10 +272,10 @@ fesa::AnalysisState makeShellPhysicalState(
|
||||
auto state = fesa::AnalysisState::create(
|
||||
*fixture.dofs, {"Step-1", 0U});
|
||||
for (std::size_t node = 0U;
|
||||
node < fixture.domain->nodes().size();
|
||||
node < fixture.domain->Nodes().size();
|
||||
++node) {
|
||||
const double x = fixture.domain->nodes()[node].coordinates[0U];
|
||||
const double y = fixture.domain->nodes()[node].coordinates[1U];
|
||||
const double x = fixture.domain->Nodes()[node].coordinates[0U];
|
||||
const double y = fixture.domain->Nodes()[node].coordinates[1U];
|
||||
const std::size_t offset = 6U * node;
|
||||
state.displacement()[offset] =
|
||||
generalized[0U] * x + 0.5 * generalized[2U] * y;
|
||||
@@ -345,19 +345,19 @@ void expectScaledNear(
|
||||
|
||||
std::vector<fesa::EndpointResultRow> makeStationRows(
|
||||
const RecoveryFixture& fixture) {
|
||||
const auto& nodes = fixture.domain->nodes();
|
||||
const auto& elements = fixture.domain->elements();
|
||||
const auto& nodes = fixture.domain->Nodes();
|
||||
const auto& elements = fixture.domain->Elements();
|
||||
return {
|
||||
{0U, 0, nodes[elements[0U].nodeIndices[0U]].sourceId,
|
||||
{0U, 0, nodes[elements[0U].node_indices[0U]].source_id,
|
||||
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
|
||||
{1.0, 2.0, 3.0, 4.0}},
|
||||
{0U, 1, nodes[elements[0U].nodeIndices[1U]].sourceId,
|
||||
{0U, 1, nodes[elements[0U].node_indices[1U]].source_id,
|
||||
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
|
||||
{5.0, 6.0, 7.0, 8.0}},
|
||||
{1U, 0, nodes[elements[1U].nodeIndices[0U]].sourceId,
|
||||
{1U, 0, nodes[elements[1U].node_indices[0U]].source_id,
|
||||
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
|
||||
{5.0, 6.0, 7.0, 8.0}},
|
||||
{1U, 1, nodes[elements[1U].nodeIndices[1U]].sourceId,
|
||||
{1U, 1, nodes[elements[1U].node_indices[1U]].source_id,
|
||||
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
|
||||
{9.0, 10.0, 11.0, 12.0}}};
|
||||
}
|
||||
@@ -639,10 +639,10 @@ TEST(ResultRecovery, RecoversShellRowsInStableElementAndGpOrder) {
|
||||
auto state = fesa::AnalysisState::create(
|
||||
*fixture.dofs, {"Step-1", 0U});
|
||||
for (std::size_t node = 0U;
|
||||
node < fixture.domain->nodes().size();
|
||||
node < fixture.domain->Nodes().size();
|
||||
++node) {
|
||||
const double x = fixture.domain->nodes()[node].coordinates[0U];
|
||||
const double y = fixture.domain->nodes()[node].coordinates[1U];
|
||||
const double x = fixture.domain->Nodes()[node].coordinates[0U];
|
||||
const double y = fixture.domain->Nodes()[node].coordinates[1U];
|
||||
state.displacement()[node * 6U] = 0.1 * x + 0.1 * y;
|
||||
state.displacement()[node * 6U + 1U] = -0.05 * y + 0.1 * x;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "fesa/analysis/analysis_state.hpp"
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/model/domain.hpp"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <type_traits>
|
||||
|
||||
Reference in New Issue
Block a user