feat(cpp-object-oriented-modular-refactoring): step 12 - material-property-hierarchy
This commit is contained in:
@@ -8,6 +8,9 @@
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Identifies a semantic entity by its stable collection position.
|
||||
using EntityIndex = std::uint32_t;
|
||||
|
||||
/// @brief Identifies the input location that produced an item or diagnostic.
|
||||
struct SourceLocation {
|
||||
std::filesystem::path file;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#ifndef FESA_MATERIALS_ISOTROPIC_LINEAR_ELASTIC_MATERIAL_H_
|
||||
#define FESA_MATERIALS_ISOTROPIC_LINEAR_ELASTIC_MATERIAL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/materials/material.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Stores homogeneous isotropic linear-elastic material data.
|
||||
/// @note Poisson ratios above 0.5 remain valid for the approved beam subset;
|
||||
/// shell compatibility is checked by the shell kernel.
|
||||
class IsotropicLinearElasticMaterial final : public Material {
|
||||
public:
|
||||
/// @brief Creates a material after validating the current elastic fields.
|
||||
/// @param source_id Stable semantic identity supplied by the mapper.
|
||||
/// @param name Source material name.
|
||||
/// @param youngs_modulus Young's modulus in the active consistent unit
|
||||
/// system.
|
||||
/// @param poissons_ratio Dimensionless Poisson ratio.
|
||||
/// @param location Source MATERIAL keyword location.
|
||||
/// @return A material or a structured model failure.
|
||||
static Result<IsotropicLinearElasticMaterial> Create(SourceEntityId source_id,
|
||||
std::string name,
|
||||
double youngs_modulus,
|
||||
double poissons_ratio,
|
||||
SourceLocation location);
|
||||
|
||||
/// @brief Constructs an already validated parser-owned material record.
|
||||
/// @note This compatibility seam preserves existing semantic mapping until
|
||||
/// Domain polymorphic ownership is migrated.
|
||||
IsotropicLinearElasticMaterial(std::string name, double youngs_modulus,
|
||||
double poissons_ratio,
|
||||
SourceLocation location);
|
||||
|
||||
MaterialKind Kind() const noexcept override;
|
||||
const SourceEntityId& SourceId() const noexcept override;
|
||||
const SourceLocation& Location() const noexcept override;
|
||||
|
||||
/// @brief Returns the source material name.
|
||||
const std::string& Name() const noexcept;
|
||||
|
||||
/// @brief Returns Young's modulus in the active consistent unit system.
|
||||
double YoungsModulus() const noexcept;
|
||||
|
||||
/// @brief Returns the dimensionless Poisson ratio.
|
||||
double PoissonsRatio() const noexcept;
|
||||
|
||||
// Public storage preserves the current semantic-record API until Domain
|
||||
// ownership migrates in the next approved Step.
|
||||
std::string name;
|
||||
double youngs_modulus;
|
||||
double poisson_ratio;
|
||||
SourceLocation location;
|
||||
|
||||
private:
|
||||
/// @brief Constructs a candidate whose fields have already been checked.
|
||||
IsotropicLinearElasticMaterial(SourceEntityId source_id, std::string name,
|
||||
double youngs_modulus, double poissons_ratio,
|
||||
SourceLocation location);
|
||||
|
||||
SourceEntityId source_id_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_MATERIALS_ISOTROPIC_LINEAR_ELASTIC_MATERIAL_H_
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef FESA_MATERIALS_MATERIAL_H_
|
||||
#define FESA_MATERIALS_MATERIAL_H_
|
||||
|
||||
#include "fesa/core/source_identity.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Identifies the supported concrete material semantics.
|
||||
enum class MaterialKind { kIsotropicLinearElastic };
|
||||
|
||||
/// @brief Provides stable identity for a Domain-owned material definition.
|
||||
class Material {
|
||||
public:
|
||||
virtual ~Material() = default;
|
||||
|
||||
/// @brief Returns the concrete material kind.
|
||||
virtual MaterialKind Kind() const noexcept = 0;
|
||||
|
||||
/// @brief Returns the stable source identity preserved for diagnostics.
|
||||
virtual const SourceEntityId& SourceId() const noexcept = 0;
|
||||
|
||||
/// @brief Returns the input location that defined the material.
|
||||
virtual const SourceLocation& Location() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_MATERIALS_MATERIAL_H_
|
||||
@@ -11,12 +11,12 @@
|
||||
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/core/source_identity.h"
|
||||
#include "fesa/materials/isotropic_linear_elastic_material.h"
|
||||
#include "fesa/properties/general_beam_section.h"
|
||||
#include "fesa/properties/shell_section.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;
|
||||
@@ -24,26 +24,8 @@ struct Node {
|
||||
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 Keeps the current material spelling during the hierarchy migration.
|
||||
using LinearElasticMaterial = IsotropicLinearElasticMaterial;
|
||||
|
||||
/// @brief Preserves the source shell element label independently of
|
||||
/// formulation.
|
||||
@@ -52,14 +34,6 @@ 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;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef FESA_PROPERTIES_ELEMENT_PROPERTY_H_
|
||||
#define FESA_PROPERTIES_ELEMENT_PROPERTY_H_
|
||||
|
||||
#include "fesa/core/source_identity.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Identifies the supported concrete element-property semantics.
|
||||
enum class ElementPropertyKind { kGeneralBeamSection, kShellSection };
|
||||
|
||||
/// @brief Provides stable identity for a Domain-owned element property.
|
||||
class ElementProperty {
|
||||
public:
|
||||
virtual ~ElementProperty() = default;
|
||||
|
||||
/// @brief Returns the concrete property kind.
|
||||
virtual ElementPropertyKind Kind() const noexcept = 0;
|
||||
|
||||
/// @brief Returns the stable source identity preserved for diagnostics.
|
||||
virtual const SourceEntityId& SourceId() const noexcept = 0;
|
||||
|
||||
/// @brief Returns the input location that defined the property.
|
||||
virtual const SourceLocation& Location() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_PROPERTIES_ELEMENT_PROPERTY_H_
|
||||
@@ -0,0 +1,83 @@
|
||||
#ifndef FESA_PROPERTIES_GENERAL_BEAM_SECTION_H_
|
||||
#define FESA_PROPERTIES_GENERAL_BEAM_SECTION_H_
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/properties/element_property.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Stores the approved general Euler-beam section properties.
|
||||
class GeneralBeamSection final : public ElementProperty {
|
||||
public:
|
||||
/// @brief Creates a validated general beam section.
|
||||
/// @param first_axis Abaqus first section axis in global coordinates.
|
||||
/// @param section_points Optional section-point coordinates `(x1,x2)`.
|
||||
/// @return A section or a structured model failure.
|
||||
static Result<GeneralBeamSection> Create(
|
||||
SourceEntityId source_id, 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 Constructs an already validated parser-owned section record.
|
||||
/// @note This compatibility seam preserves current consumers until Domain
|
||||
/// polymorphic ownership is migrated.
|
||||
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);
|
||||
|
||||
ElementPropertyKind Kind() const noexcept override;
|
||||
const SourceEntityId& SourceId() const noexcept override;
|
||||
const SourceLocation& Location() const noexcept override;
|
||||
|
||||
/// @brief Returns the source section name.
|
||||
const std::string& Name() const noexcept;
|
||||
/// @brief Returns cross-sectional area.
|
||||
double Area() const noexcept;
|
||||
/// @brief Returns the first principal section inertia input.
|
||||
double I11() const noexcept;
|
||||
/// @brief Returns the cross-bending inertia, which is exactly zero in V0.
|
||||
double I12() const noexcept;
|
||||
/// @brief Returns the second principal section inertia input.
|
||||
double I22() const noexcept;
|
||||
/// @brief Returns the Saint-Venant torsional constant.
|
||||
double TorsionalConstant() const noexcept;
|
||||
/// @brief Returns the Abaqus first section axis in global coordinates.
|
||||
const std::array<double, 3>& FirstAxis() const noexcept;
|
||||
/// @brief Returns optional section points in source order.
|
||||
const std::vector<std::array<double, 2>>& SectionPoints() const noexcept;
|
||||
|
||||
// Public storage preserves the current semantic-record API until Domain
|
||||
// ownership migrates in the next approved Step.
|
||||
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;
|
||||
|
||||
private:
|
||||
/// @brief Constructs a candidate whose fields have already been checked.
|
||||
GeneralBeamSection(SourceEntityId source_id, 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);
|
||||
|
||||
SourceEntityId source_id_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_PROPERTIES_GENERAL_BEAM_SECTION_H_
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef FESA_PROPERTIES_SHELL_SECTION_H_
|
||||
#define FESA_PROPERTIES_SHELL_SECTION_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/properties/element_property.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Stores a centered constant-thickness shell section assignment.
|
||||
class ShellSection final : public ElementProperty {
|
||||
public:
|
||||
/// @brief Creates a validated shell section.
|
||||
/// @param thickness Positive thickness in the active consistent unit system.
|
||||
/// @param material_index Stable Domain material collection position.
|
||||
/// @return A section or a structured model failure.
|
||||
static Result<ShellSection> Create(SourceEntityId source_id, std::string name,
|
||||
double thickness,
|
||||
EntityIndex material_index,
|
||||
SourceLocation location);
|
||||
|
||||
/// @brief Constructs an already validated parser-owned shell record.
|
||||
/// @note This compatibility seam preserves current consumers until Domain
|
||||
/// polymorphic ownership is migrated.
|
||||
ShellSection(std::string name, double thickness, EntityIndex material_index,
|
||||
SourceLocation location);
|
||||
|
||||
ElementPropertyKind Kind() const noexcept override;
|
||||
const SourceEntityId& SourceId() const noexcept override;
|
||||
const SourceLocation& Location() const noexcept override;
|
||||
|
||||
/// @brief Returns the source shell-section name.
|
||||
const std::string& Name() const noexcept;
|
||||
/// @brief Returns centered shell thickness.
|
||||
double Thickness() const noexcept;
|
||||
/// @brief Returns the stable Domain material collection position.
|
||||
EntityIndex MaterialIndex() const noexcept;
|
||||
|
||||
// Public storage preserves the current semantic-record API until Domain
|
||||
// ownership migrates in the next approved Step.
|
||||
std::string name;
|
||||
double thickness;
|
||||
EntityIndex material_index;
|
||||
SourceLocation location;
|
||||
|
||||
private:
|
||||
/// @brief Constructs a candidate whose fields have already been checked.
|
||||
ShellSection(SourceEntityId source_id, std::string name, double thickness,
|
||||
EntityIndex material_index, SourceLocation location);
|
||||
|
||||
SourceEntityId source_id_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_PROPERTIES_SHELL_SECTION_H_
|
||||
@@ -19,6 +19,7 @@ add_library(
|
||||
io/abaqus/domain_mapper.cpp
|
||||
io/abaqus/input_reader.cpp
|
||||
io/hdf5/hdf5_results_writer.cpp
|
||||
materials/isotropic_linear_elastic_material.cpp
|
||||
math/dense_blas_internal.cpp
|
||||
math/matrix.cpp
|
||||
math/sparse_matrix.cpp
|
||||
@@ -26,6 +27,8 @@ add_library(
|
||||
model/domain.cpp
|
||||
model/shell_geometry.cpp
|
||||
model/source_target_resolver.cpp
|
||||
properties/general_beam_section.cpp
|
||||
properties/shell_section.cpp
|
||||
results/result_recovery.cpp
|
||||
solvers/linear/mkl_pardiso_solver.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "fesa/materials/isotropic_linear_elastic_material.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
Status InvalidMaterial(const SourceEntityId& source_id,
|
||||
const SourceLocation& location,
|
||||
const std::string& message) {
|
||||
return Status::Failure(FailureCategory::kModel,
|
||||
{{Severity::kError, "invalid-material", location,
|
||||
"*MATERIAL", source_id.source_label_text, message}});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result<IsotropicLinearElasticMaterial> IsotropicLinearElasticMaterial::Create(
|
||||
SourceEntityId source_id, std::string name, double youngs_modulus,
|
||||
double poissons_ratio, SourceLocation location) {
|
||||
if (name.empty() || source_id.source_label_text.empty()) {
|
||||
return Result<IsotropicLinearElasticMaterial>::Failure(InvalidMaterial(
|
||||
source_id, location, "Material source identity must not be empty."));
|
||||
}
|
||||
|
||||
const double denominator = 2.0 * (1.0 + poissons_ratio);
|
||||
const double shear_modulus = youngs_modulus / denominator;
|
||||
if (!std::isfinite(youngs_modulus) || !(youngs_modulus > 0.0) ||
|
||||
!std::isfinite(poissons_ratio) || !std::isfinite(shear_modulus) ||
|
||||
!(shear_modulus > 0.0)) {
|
||||
return Result<IsotropicLinearElasticMaterial>::Failure(InvalidMaterial(
|
||||
source_id, location,
|
||||
"E and the derived G=E/(2*(1+nu)) must be finite and positive."));
|
||||
}
|
||||
|
||||
return Result<IsotropicLinearElasticMaterial>::Success(
|
||||
IsotropicLinearElasticMaterial{std::move(source_id), std::move(name),
|
||||
youngs_modulus, poissons_ratio,
|
||||
std::move(location)});
|
||||
}
|
||||
|
||||
IsotropicLinearElasticMaterial::IsotropicLinearElasticMaterial(
|
||||
std::string name_value, double youngs_modulus_value,
|
||||
double poissons_ratio_value, SourceLocation location_value)
|
||||
: name{std::move(name_value)},
|
||||
youngs_modulus{youngs_modulus_value},
|
||||
poisson_ratio{poissons_ratio_value},
|
||||
location{std::move(location_value)},
|
||||
source_id_{"", 0, name} {}
|
||||
|
||||
MaterialKind IsotropicLinearElasticMaterial::Kind() const noexcept {
|
||||
return MaterialKind::kIsotropicLinearElastic;
|
||||
}
|
||||
|
||||
const SourceEntityId& IsotropicLinearElasticMaterial::SourceId()
|
||||
const noexcept {
|
||||
return source_id_;
|
||||
}
|
||||
|
||||
const SourceLocation& IsotropicLinearElasticMaterial::Location()
|
||||
const noexcept {
|
||||
return location;
|
||||
}
|
||||
|
||||
const std::string& IsotropicLinearElasticMaterial::Name() const noexcept {
|
||||
return name;
|
||||
}
|
||||
|
||||
double IsotropicLinearElasticMaterial::YoungsModulus() const noexcept {
|
||||
return youngs_modulus;
|
||||
}
|
||||
|
||||
double IsotropicLinearElasticMaterial::PoissonsRatio() const noexcept {
|
||||
return poisson_ratio;
|
||||
}
|
||||
|
||||
IsotropicLinearElasticMaterial::IsotropicLinearElasticMaterial(
|
||||
SourceEntityId source_id, std::string name_value,
|
||||
double youngs_modulus_value, double poissons_ratio_value,
|
||||
SourceLocation location_value)
|
||||
: name{std::move(name_value)},
|
||||
youngs_modulus{youngs_modulus_value},
|
||||
poisson_ratio{poissons_ratio_value},
|
||||
location{std::move(location_value)},
|
||||
source_id_{std::move(source_id)} {}
|
||||
|
||||
} // namespace fesa
|
||||
@@ -0,0 +1,140 @@
|
||||
#include "fesa/properties/general_beam_section.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
Status InvalidBeamSection(const std::string& code,
|
||||
const SourceEntityId& source_id,
|
||||
const SourceLocation& location,
|
||||
const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kModel,
|
||||
{{Severity::kError, code, location, "*BEAM GENERAL SECTION",
|
||||
source_id.source_label_text, message}});
|
||||
}
|
||||
|
||||
bool IsFinitePoint(const std::array<double, 2>& point) {
|
||||
return std::isfinite(point[0]) && std::isfinite(point[1]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result<GeneralBeamSection> GeneralBeamSection::Create(
|
||||
SourceEntityId source_id, 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) {
|
||||
if (name.empty() || source_id.source_label_text.empty()) {
|
||||
return Result<GeneralBeamSection>::Failure(
|
||||
InvalidBeamSection("invalid-beam-property", source_id, location,
|
||||
"Beam section source identity must not be empty."));
|
||||
}
|
||||
if (!std::isfinite(i12) || i12 != 0.0) {
|
||||
return Result<GeneralBeamSection>::Failure(InvalidBeamSection(
|
||||
std::isfinite(i12) ? "unsupported-coupled-section"
|
||||
: "invalid-beam-property",
|
||||
source_id, location,
|
||||
"The general beam section requires finite exact I12=0."));
|
||||
}
|
||||
const std::array<double, 4> positive_properties = {area, i11, i22,
|
||||
torsional_constant};
|
||||
if (std::any_of(positive_properties.begin(), positive_properties.end(),
|
||||
[](double property) {
|
||||
return !std::isfinite(property) || !(property > 0.0);
|
||||
}) ||
|
||||
std::any_of(first_axis.begin(), first_axis.end(),
|
||||
[](double component) { return !std::isfinite(component); })) {
|
||||
return Result<GeneralBeamSection>::Failure(InvalidBeamSection(
|
||||
"invalid-beam-property", source_id, location,
|
||||
"A, I11, I22, J, and first-axis components must be finite; section "
|
||||
"magnitudes must be positive."));
|
||||
}
|
||||
if (std::any_of(section_points.begin(), section_points.end(),
|
||||
[](const auto& point) { return !IsFinitePoint(point); })) {
|
||||
return Result<GeneralBeamSection>::Failure(
|
||||
InvalidBeamSection("invalid-section-point", source_id, location,
|
||||
"Section-point coordinates must be finite."));
|
||||
}
|
||||
|
||||
return Result<GeneralBeamSection>::Success(
|
||||
GeneralBeamSection{std::move(source_id), std::move(name), area, i11, i12,
|
||||
i22, torsional_constant, first_axis,
|
||||
std::move(section_points), std::move(location)});
|
||||
}
|
||||
|
||||
GeneralBeamSection::GeneralBeamSection(
|
||||
std::string name_value, double area_value, double i11_value,
|
||||
double i12_value, double i22_value, double torsional_constant_value,
|
||||
std::array<double, 3> first_axis_value,
|
||||
std::vector<std::array<double, 2>> section_points_value,
|
||||
SourceLocation location_value)
|
||||
: name{std::move(name_value)},
|
||||
area{area_value},
|
||||
i11{i11_value},
|
||||
i12{i12_value},
|
||||
i22{i22_value},
|
||||
torsional_constant{torsional_constant_value},
|
||||
first_axis{first_axis_value},
|
||||
section_points{std::move(section_points_value)},
|
||||
location{std::move(location_value)},
|
||||
source_id_{"", 0, name} {}
|
||||
|
||||
ElementPropertyKind GeneralBeamSection::Kind() const noexcept {
|
||||
return ElementPropertyKind::kGeneralBeamSection;
|
||||
}
|
||||
|
||||
const SourceEntityId& GeneralBeamSection::SourceId() const noexcept {
|
||||
return source_id_;
|
||||
}
|
||||
|
||||
const SourceLocation& GeneralBeamSection::Location() const noexcept {
|
||||
return location;
|
||||
}
|
||||
|
||||
const std::string& GeneralBeamSection::Name() const noexcept { return name; }
|
||||
|
||||
double GeneralBeamSection::Area() const noexcept { return area; }
|
||||
|
||||
double GeneralBeamSection::I11() const noexcept { return i11; }
|
||||
|
||||
double GeneralBeamSection::I12() const noexcept { return i12; }
|
||||
|
||||
double GeneralBeamSection::I22() const noexcept { return i22; }
|
||||
|
||||
double GeneralBeamSection::TorsionalConstant() const noexcept {
|
||||
return torsional_constant;
|
||||
}
|
||||
|
||||
const std::array<double, 3>& GeneralBeamSection::FirstAxis() const noexcept {
|
||||
return first_axis;
|
||||
}
|
||||
|
||||
const std::vector<std::array<double, 2>>& GeneralBeamSection::SectionPoints()
|
||||
const noexcept {
|
||||
return section_points;
|
||||
}
|
||||
|
||||
GeneralBeamSection::GeneralBeamSection(
|
||||
SourceEntityId source_id, std::string name_value, double area_value,
|
||||
double i11_value, double i12_value, double i22_value,
|
||||
double torsional_constant_value, std::array<double, 3> first_axis_value,
|
||||
std::vector<std::array<double, 2>> section_points_value,
|
||||
SourceLocation location_value)
|
||||
: name{std::move(name_value)},
|
||||
area{area_value},
|
||||
i11{i11_value},
|
||||
i12{i12_value},
|
||||
i22{i22_value},
|
||||
torsional_constant{torsional_constant_value},
|
||||
first_axis{first_axis_value},
|
||||
section_points{std::move(section_points_value)},
|
||||
location{std::move(location_value)},
|
||||
source_id_{std::move(source_id)} {}
|
||||
|
||||
} // namespace fesa
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "fesa/properties/shell_section.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
Status InvalidShellSection(const SourceEntityId& source_id,
|
||||
const SourceLocation& location,
|
||||
const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kModel,
|
||||
{{Severity::kError, "invalid-shell-thickness", location, "*SHELL SECTION",
|
||||
source_id.source_label_text, message}});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result<ShellSection> ShellSection::Create(SourceEntityId source_id,
|
||||
std::string name, double thickness,
|
||||
EntityIndex material_index,
|
||||
SourceLocation location) {
|
||||
if (name.empty() || source_id.source_label_text.empty()) {
|
||||
return Result<ShellSection>::Failure(InvalidShellSection(
|
||||
source_id, location,
|
||||
"Shell section source identity must not be empty."));
|
||||
}
|
||||
if (!std::isfinite(thickness) || !(thickness > 0.0)) {
|
||||
return Result<ShellSection>::Failure(InvalidShellSection(
|
||||
source_id, location, "Shell thickness must be finite and positive."));
|
||||
}
|
||||
return Result<ShellSection>::Success(
|
||||
ShellSection{std::move(source_id), std::move(name), thickness,
|
||||
material_index, std::move(location)});
|
||||
}
|
||||
|
||||
ShellSection::ShellSection(std::string name_value, double thickness_value,
|
||||
EntityIndex material_index_value,
|
||||
SourceLocation location_value)
|
||||
: name{std::move(name_value)},
|
||||
thickness{thickness_value},
|
||||
material_index{material_index_value},
|
||||
location{std::move(location_value)},
|
||||
source_id_{"", 0, name} {}
|
||||
|
||||
ElementPropertyKind ShellSection::Kind() const noexcept {
|
||||
return ElementPropertyKind::kShellSection;
|
||||
}
|
||||
|
||||
const SourceEntityId& ShellSection::SourceId() const noexcept {
|
||||
return source_id_;
|
||||
}
|
||||
|
||||
const SourceLocation& ShellSection::Location() const noexcept {
|
||||
return location;
|
||||
}
|
||||
|
||||
const std::string& ShellSection::Name() const noexcept { return name; }
|
||||
|
||||
double ShellSection::Thickness() const noexcept { return thickness; }
|
||||
|
||||
EntityIndex ShellSection::MaterialIndex() const noexcept {
|
||||
return material_index;
|
||||
}
|
||||
|
||||
ShellSection::ShellSection(SourceEntityId source_id, std::string name_value,
|
||||
double thickness_value,
|
||||
EntityIndex material_index_value,
|
||||
SourceLocation location_value)
|
||||
: name{std::move(name_value)},
|
||||
thickness{thickness_value},
|
||||
material_index{material_index_value},
|
||||
location{std::move(location_value)},
|
||||
source_id_{std::move(source_id)} {}
|
||||
|
||||
} // namespace fesa
|
||||
@@ -29,6 +29,8 @@ add_executable(
|
||||
unit/model/model_types_test.cpp
|
||||
unit/model/shell_geometry_test.cpp
|
||||
unit/model/source_target_resolver_test.cpp
|
||||
unit/materials/material_test.cpp
|
||||
unit/properties/element_property_test.cpp
|
||||
unit/results/result_records_test.cpp
|
||||
unit/results/result_recovery_test.cpp
|
||||
unit/results/results_writer_test.cpp
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/materials/isotropic_linear_elastic_material.h"
|
||||
|
||||
namespace {
|
||||
|
||||
fesa::SourceEntityId MaterialSourceId() { return {"", 7, "Steel"}; }
|
||||
|
||||
fesa::SourceLocation MaterialLocation() { return {"models/material.inp", 30U}; }
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Material, OwnsIsotropicElasticityThroughVirtualBase) {
|
||||
static_assert(std::has_virtual_destructor_v<fesa::Material>);
|
||||
|
||||
auto candidate = fesa::IsotropicLinearElasticMaterial::Create(
|
||||
MaterialSourceId(), "Steel", 210.0e9, 0.3, MaterialLocation());
|
||||
ASSERT_TRUE(candidate.HasValue());
|
||||
|
||||
std::unique_ptr<fesa::Material> material =
|
||||
std::make_unique<fesa::IsotropicLinearElasticMaterial>(
|
||||
std::move(candidate.Value()));
|
||||
EXPECT_EQ(material->Kind(), fesa::MaterialKind::kIsotropicLinearElastic);
|
||||
EXPECT_EQ(material->SourceId().source_label, 7);
|
||||
EXPECT_EQ(material->SourceId().source_label_text, "Steel");
|
||||
|
||||
const auto& isotropic =
|
||||
static_cast<const fesa::IsotropicLinearElasticMaterial&>(*material);
|
||||
EXPECT_EQ(isotropic.Name(), "Steel");
|
||||
EXPECT_DOUBLE_EQ(isotropic.YoungsModulus(), 210.0e9);
|
||||
EXPECT_DOUBLE_EQ(isotropic.PoissonsRatio(), 0.3);
|
||||
EXPECT_EQ(isotropic.Location().file, "models/material.inp");
|
||||
EXPECT_EQ(isotropic.Location().line, 30U);
|
||||
}
|
||||
|
||||
TEST(Material, RejectsInvalidCurrentElasticFields) {
|
||||
const auto expect_rejected = [](double youngs_modulus,
|
||||
double poissons_ratio) {
|
||||
const auto result = fesa::IsotropicLinearElasticMaterial::Create(
|
||||
MaterialSourceId(), "Steel", youngs_modulus, poissons_ratio,
|
||||
MaterialLocation());
|
||||
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].severity,
|
||||
fesa::Severity::kError);
|
||||
};
|
||||
|
||||
expect_rejected(0.0, 0.3);
|
||||
expect_rejected(std::numeric_limits<double>::infinity(), 0.3);
|
||||
expect_rejected(210.0e9, -1.0);
|
||||
expect_rejected(210.0e9, std::numeric_limits<double>::quiet_NaN());
|
||||
|
||||
const auto beam_compatible = fesa::IsotropicLinearElasticMaterial::Create(
|
||||
MaterialSourceId(), "Steel", 210.0e9, 0.75, MaterialLocation());
|
||||
EXPECT_TRUE(beam_compatible.HasValue());
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/properties/general_beam_section.h"
|
||||
#include "fesa/properties/shell_section.h"
|
||||
|
||||
namespace {
|
||||
|
||||
fesa::SourceLocation PropertyLocation() { return {"models/property.inp", 40U}; }
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ElementProperty, OwnsGeneralBeamSectionThroughVirtualBase) {
|
||||
static_assert(std::has_virtual_destructor_v<fesa::ElementProperty>);
|
||||
|
||||
const fesa::SourceEntityId source_id{"", 4, "BeamSection"};
|
||||
auto candidate = fesa::GeneralBeamSection::Create(
|
||||
source_id, "BeamSection", 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}}, PropertyLocation());
|
||||
ASSERT_TRUE(candidate.HasValue());
|
||||
|
||||
std::unique_ptr<fesa::ElementProperty> property =
|
||||
std::make_unique<fesa::GeneralBeamSection>(std::move(candidate.Value()));
|
||||
EXPECT_EQ(property->Kind(), fesa::ElementPropertyKind::kGeneralBeamSection);
|
||||
EXPECT_EQ(property->SourceId().source_label, 4);
|
||||
EXPECT_EQ(property->SourceId().source_label_text, "BeamSection");
|
||||
|
||||
const auto& section = static_cast<const fesa::GeneralBeamSection&>(*property);
|
||||
EXPECT_EQ(section.Name(), "BeamSection");
|
||||
EXPECT_DOUBLE_EQ(section.Area(), 0.02);
|
||||
EXPECT_DOUBLE_EQ(section.I11(), 1.0e-5);
|
||||
EXPECT_DOUBLE_EQ(section.I12(), 0.0);
|
||||
EXPECT_DOUBLE_EQ(section.I22(), 2.0e-5);
|
||||
EXPECT_DOUBLE_EQ(section.TorsionalConstant(), 5.0e-6);
|
||||
EXPECT_EQ(section.FirstAxis(), (std::array<double, 3>{0.0, 1.0, 0.0}));
|
||||
EXPECT_EQ(section.SectionPoints(),
|
||||
(std::vector<std::array<double, 2>>{{-0.1, 0.0}, {0.1, 0.0}}));
|
||||
EXPECT_EQ(section.Location().line, 40U);
|
||||
}
|
||||
|
||||
TEST(ElementProperty, OwnsShellSectionThroughVirtualBase) {
|
||||
const fesa::SourceEntityId source_id{"", 8, "ShellSection"};
|
||||
auto candidate =
|
||||
fesa::ShellSection::Create(source_id, "ShellSection", 0.01,
|
||||
fesa::EntityIndex{3}, PropertyLocation());
|
||||
ASSERT_TRUE(candidate.HasValue());
|
||||
|
||||
std::unique_ptr<fesa::ElementProperty> property =
|
||||
std::make_unique<fesa::ShellSection>(std::move(candidate.Value()));
|
||||
EXPECT_EQ(property->Kind(), fesa::ElementPropertyKind::kShellSection);
|
||||
EXPECT_EQ(property->SourceId().source_label, 8);
|
||||
|
||||
const auto& section = static_cast<const fesa::ShellSection&>(*property);
|
||||
EXPECT_EQ(section.Name(), "ShellSection");
|
||||
EXPECT_DOUBLE_EQ(section.Thickness(), 0.01);
|
||||
EXPECT_EQ(section.MaterialIndex(), fesa::EntityIndex{3});
|
||||
EXPECT_EQ(section.Location().file, "models/property.inp");
|
||||
}
|
||||
|
||||
TEST(ElementProperty, PreservesEmptyOptionalSectionPointInventory) {
|
||||
const auto section = fesa::GeneralBeamSection::Create(
|
||||
{"", 4, "BeamSection"}, "BeamSection", 0.02, 1.0e-5, 0.0, 2.0e-5, 5.0e-6,
|
||||
{0.0, 1.0, 0.0}, {}, PropertyLocation());
|
||||
ASSERT_TRUE(section.HasValue());
|
||||
EXPECT_TRUE(section.Value().SectionPoints().empty());
|
||||
}
|
||||
|
||||
TEST(ElementProperty, RejectsInvalidCurrentSectionFields) {
|
||||
const auto invalid_beam = fesa::GeneralBeamSection::Create(
|
||||
{"", 4, "BeamSection"}, "BeamSection", 0.0, 1.0e-5, 0.0, 2.0e-5, 5.0e-6,
|
||||
{0.0, 1.0, 0.0}, {}, PropertyLocation());
|
||||
ASSERT_FALSE(invalid_beam.HasValue());
|
||||
EXPECT_EQ(invalid_beam.GetStatus().Category(), fesa::FailureCategory::kModel);
|
||||
|
||||
const auto coupled_beam = fesa::GeneralBeamSection::Create(
|
||||
{"", 4, "BeamSection"}, "BeamSection", 0.02, 1.0e-5, 1.0e-8, 2.0e-5,
|
||||
5.0e-6, {0.0, 1.0, 0.0}, {}, PropertyLocation());
|
||||
EXPECT_FALSE(coupled_beam.HasValue());
|
||||
|
||||
const auto nonfinite_point = fesa::GeneralBeamSection::Create(
|
||||
{"", 4, "BeamSection"}, "BeamSection", 0.02, 1.0e-5, 0.0, 2.0e-5, 5.0e-6,
|
||||
{0.0, 1.0, 0.0}, {{std::numeric_limits<double>::quiet_NaN(), 0.0}},
|
||||
PropertyLocation());
|
||||
EXPECT_FALSE(nonfinite_point.HasValue());
|
||||
|
||||
const auto invalid_shell =
|
||||
fesa::ShellSection::Create({"", 8, "ShellSection"}, "ShellSection", 0.0,
|
||||
fesa::EntityIndex{3}, PropertyLocation());
|
||||
ASSERT_FALSE(invalid_shell.HasValue());
|
||||
EXPECT_EQ(invalid_shell.GetStatus().Category(),
|
||||
fesa::FailureCategory::kModel);
|
||||
}
|
||||
Reference in New Issue
Block a user