feat(cpp-object-oriented-modular-refactoring): step 18 - load-hierarchy
This commit is contained in:
@@ -26,6 +26,7 @@ add_executable(
|
||||
unit/io/abaqus/input_reader_test.cpp
|
||||
unit/io/abaqus/input_syntax_test.cpp
|
||||
unit/io/hdf5/hdf5_results_writer_test.cpp
|
||||
unit/loads/load_test.cpp
|
||||
unit/model/domain_test.cpp
|
||||
unit/model/model_types_test.cpp
|
||||
unit/model/shell_geometry_test.cpp
|
||||
|
||||
@@ -109,6 +109,9 @@ TEST(AnalysisModel, ClassifiesActiveEntitiesInStableOrder) {
|
||||
EXPECT_EQ(model.ActiveBoundaryConditions(),
|
||||
(std::vector<fesa::EntityIndex>{0U, 1U}));
|
||||
EXPECT_EQ(model.ActiveLoads(), (std::vector<fesa::EntityIndex>{0U, 1U, 2U}));
|
||||
ASSERT_EQ(model.Step().Loads().size(), 3U);
|
||||
EXPECT_EQ(&model.Step().Loads()[0U].get(),
|
||||
&domain_result.Value().Steps()[0U].Loads()[0U].get());
|
||||
}
|
||||
|
||||
TEST(AnalysisModel, ReferencesWithoutCopyingOrMutatingDomain) {
|
||||
@@ -118,8 +121,9 @@ TEST(AnalysisModel, ReferencesWithoutCopyingOrMutatingDomain) {
|
||||
const auto* const element_address = &domain.Elements()[0U];
|
||||
const auto* const material_address = &domain.Materials()[0U];
|
||||
const auto* const section_address = &domain.Sections()[0U];
|
||||
const std::string step_name = domain.Steps()[0].name;
|
||||
const double first_load_magnitude = domain.Steps()[0].loads[0].magnitude;
|
||||
const std::string step_name = domain.Steps()[0].Name();
|
||||
const double first_load_magnitude =
|
||||
domain.Steps()[0].ConcentratedLoads()[0U].GlobalComponents()[0U];
|
||||
|
||||
auto model_result = fesa::AnalysisModel::Create(domain);
|
||||
ASSERT_TRUE(model_result.HasValue());
|
||||
@@ -136,8 +140,10 @@ TEST(AnalysisModel, ReferencesWithoutCopyingOrMutatingDomain) {
|
||||
&domain.Materials()[2]);
|
||||
EXPECT_EQ(&model.GetDomain().Sections()[model.ActiveSections()[1]],
|
||||
&domain.Sections()[1]);
|
||||
EXPECT_EQ(domain.Steps()[0].name, step_name);
|
||||
EXPECT_DOUBLE_EQ(domain.Steps()[0].loads[0].magnitude, first_load_magnitude);
|
||||
EXPECT_EQ(domain.Steps()[0].Name(), step_name);
|
||||
EXPECT_DOUBLE_EQ(
|
||||
domain.Steps()[0].ConcentratedLoads()[0U].GlobalComponents()[0U],
|
||||
first_load_magnitude);
|
||||
}
|
||||
|
||||
// C-MODEL-002
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/loads/load.h"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
namespace {
|
||||
@@ -167,8 +168,66 @@ void ExpectFailureCode(const fesa::Result<fesa::Vector>& result,
|
||||
EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code, code);
|
||||
}
|
||||
|
||||
class FakeLoad final : public fesa::Load {
|
||||
public:
|
||||
explicit FakeLoad(std::vector<fesa::LoadContribution> contributions)
|
||||
: contributions_{std::move(contributions)} {}
|
||||
|
||||
fesa::Result<std::vector<fesa::LoadContribution>> ComputeContributions(
|
||||
const fesa::LoadContext&) const override {
|
||||
return fesa::Result<std::vector<fesa::LoadContribution>>::Success(
|
||||
contributions_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<fesa::LoadContribution> contributions_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// C-LOAD-001
|
||||
TEST(LoadAssembly, AccumulatesGenericContributionsInSuppliedSourceOrder) {
|
||||
auto fixture = MakeFixture(1U, {}, {}, {});
|
||||
const FakeLoad first(
|
||||
{{0U, 0U, 1.0e16}, {0U, 0U, -1.0e16}, {0U, 0U, 1.0}, {0U, 2U, -4.0}});
|
||||
const FakeLoad second({{1U, 2U, 1.5}, {1U, 0U, -2.0}});
|
||||
const fesa::LoadView loads{std::cref(first), std::cref(second)};
|
||||
|
||||
const auto result = fesa::LoadAssembler::AssembleFullNodalLoad(
|
||||
*fixture.model, *fixture.dofs, loads);
|
||||
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
EXPECT_DOUBLE_EQ(result.Value()[0U], -1.0);
|
||||
EXPECT_DOUBLE_EQ(result.Value()[2U], -2.5);
|
||||
}
|
||||
|
||||
TEST(LoadAssembly, RejectsGenericContributionErrorsBeforeCandidateCommit) {
|
||||
auto fixture = MakeFixture(1U, {}, {}, {});
|
||||
const FakeLoad valid({{0U, 1U, 3.0}});
|
||||
const FakeLoad nonfinite(
|
||||
{{1U, 2U, std::numeric_limits<double>::quiet_NaN()}});
|
||||
const FakeLoad out_of_range({{1U, 6U, 4.0}});
|
||||
const FakeLoad wrong_order({{3U, 2U, 4.0}});
|
||||
|
||||
ExpectFailureCode(fesa::LoadAssembler::AssembleFullNodalLoad(
|
||||
*fixture.model, *fixture.dofs,
|
||||
{std::cref(valid), std::cref(nonfinite)}),
|
||||
"nonfinite-load-value");
|
||||
ExpectFailureCode(fesa::LoadAssembler::AssembleFullNodalLoad(
|
||||
*fixture.model, *fixture.dofs,
|
||||
{std::cref(valid), std::cref(out_of_range)}),
|
||||
"invalid-load-index");
|
||||
ExpectFailureCode(fesa::LoadAssembler::AssembleFullNodalLoad(
|
||||
*fixture.model, *fixture.dofs,
|
||||
{std::cref(valid), std::cref(wrong_order)}),
|
||||
"invalid-load-order");
|
||||
|
||||
const auto repeated = fesa::LoadAssembler::AssembleFullNodalLoad(
|
||||
*fixture.model, *fixture.dofs, {std::cref(valid)});
|
||||
ASSERT_TRUE(repeated.HasValue());
|
||||
EXPECT_DOUBLE_EQ(repeated.Value()[1U], 3.0);
|
||||
}
|
||||
|
||||
TEST(LoadAssembly, AssemblesNodeSetAndSixComponentLoads) {
|
||||
const std::filesystem::path source{"models/load-assembly.inp"};
|
||||
auto fixture =
|
||||
|
||||
@@ -285,23 +285,23 @@ TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
|
||||
EXPECT_EQ(domain.BeamElements()[0U].material_index, 0U);
|
||||
EXPECT_EQ(domain.BeamElements()[0U].section_index, 0U);
|
||||
|
||||
ASSERT_EQ(domain.Steps().size(), 1U);
|
||||
ASSERT_EQ(domain.Steps().Size(), 1U);
|
||||
const auto& step = domain.Steps()[0];
|
||||
EXPECT_EQ(step.name, "Step-1");
|
||||
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].first_dof, 1);
|
||||
EXPECT_DOUBLE_EQ(step.boundaries[0].value, 0.125);
|
||||
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(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);
|
||||
ASSERT_EQ(step.Boundaries().size(), 2U);
|
||||
EXPECT_EQ(step.Boundaries()[0].target, "rootassembly");
|
||||
EXPECT_EQ(step.Boundaries()[0].first_dof, 1);
|
||||
EXPECT_DOUBLE_EQ(step.Boundaries()[0].value, 0.125);
|
||||
EXPECT_EQ(step.Boundaries()[1].last_dof, 2);
|
||||
EXPECT_DOUBLE_EQ(step.Boundaries()[1].value, 0.0);
|
||||
ASSERT_EQ(step.ConcentratedLoads().Size(), 1U);
|
||||
EXPECT_EQ(step.ConcentratedLoads()[0U].Target().target_name_or_label,
|
||||
"RootAssembly");
|
||||
EXPECT_DOUBLE_EQ(step.ConcentratedLoads()[0U].GlobalComponents()[5U], -12.5);
|
||||
EXPECT_EQ(domain.Warnings().size(), 8U);
|
||||
|
||||
const auto legacy_path = RepositoryRoot() / "reference" / "cantilever beam" /
|
||||
@@ -316,7 +316,7 @@ TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
|
||||
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().Steps().Size(), 1U);
|
||||
EXPECT_EQ(legacy.Value().Warnings().size(), 7U);
|
||||
EXPECT_EQ(ReadExactBytes(legacy_path), bytes_before);
|
||||
EXPECT_EQ(std::filesystem::last_write_time(legacy_path), timestamp_before);
|
||||
@@ -410,17 +410,24 @@ OnlySecond, 2, 5.
|
||||
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].ConcentratedLoads()[0U].Target().target_name_or_label,
|
||||
"OnlySecond");
|
||||
|
||||
auto direct =
|
||||
MapText("direct-node-labels",
|
||||
ReplaceOnce(ReplaceOnce(MinimalDeck(), "Root, 1, 6", "1, 1, 6"),
|
||||
"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]
|
||||
.ConcentratedLoads()[0U]
|
||||
.Target()
|
||||
.target_name_or_label,
|
||||
"2");
|
||||
|
||||
auto above_thresholds = MapText(
|
||||
"above-geometry-thresholds",
|
||||
@@ -543,11 +550,11 @@ TEST(InpDomainMapping, NoOpAllowlistWarnsWithoutSemanticEffect) {
|
||||
plain.Value().NodeSets().size());
|
||||
EXPECT_EQ(with_no_ops.Value().ElementSets().size(),
|
||||
plain.Value().ElementSets().size());
|
||||
EXPECT_EQ(with_no_ops.Value().Steps().size(), plain.Value().Steps().size());
|
||||
EXPECT_EQ(with_no_ops.Value().Steps()[0].boundaries.size(),
|
||||
plain.Value().Steps()[0].boundaries.size());
|
||||
EXPECT_EQ(with_no_ops.Value().Steps()[0].loads.size(),
|
||||
plain.Value().Steps()[0].loads.size());
|
||||
EXPECT_EQ(with_no_ops.Value().Steps().Size(), plain.Value().Steps().Size());
|
||||
EXPECT_EQ(with_no_ops.Value().Steps()[0].Boundaries().size(),
|
||||
plain.Value().Steps()[0].Boundaries().size());
|
||||
EXPECT_EQ(with_no_ops.Value().Steps()[0].Loads().size(),
|
||||
plain.Value().Steps()[0].Loads().size());
|
||||
}
|
||||
|
||||
// MITC4-MAP-001
|
||||
@@ -605,11 +612,12 @@ TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
|
||||
EXPECT_EQ(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].last_dof, 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].ConcentratedLoads().Size(), 1U);
|
||||
EXPECT_DOUBLE_EQ(
|
||||
domain.Steps()[0].ConcentratedLoads()[0U].GlobalComponents()[5U], 1.0);
|
||||
}
|
||||
|
||||
// MITC4-MAP-002
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/loads/concentrated_nodal_load.h"
|
||||
#include "fesa/model/domain.h"
|
||||
#include "fesa/model/source_target_resolver.h"
|
||||
|
||||
namespace {
|
||||
|
||||
fesa::Domain MakeDomain() {
|
||||
const std::filesystem::path source{"models/load-hierarchy.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:loadhierarchy";
|
||||
definition.nodes = {{{"Part-1-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 2U}},
|
||||
{{"Part-1-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 3U}}};
|
||||
definition.node_sets = {
|
||||
{"Pair", std::string{"Part-1-1"}, {0U, 1U}, {source, 4U}}};
|
||||
definition.steps = {{"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 5U}}};
|
||||
auto domain = fesa::Domain::Create(std::move(definition));
|
||||
if (!domain.HasValue()) {
|
||||
throw std::runtime_error{"Load hierarchy Domain construction failed."};
|
||||
}
|
||||
return std::move(domain.Value());
|
||||
}
|
||||
|
||||
void ExpectFailureCode(
|
||||
const fesa::Result<std::vector<fesa::LoadContribution>>& 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()[0U].code, code);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// C-LOAD-001
|
||||
TEST(Load, ConcentratedNodalLoadEmitsStableFullDofContributions) {
|
||||
static_assert(std::has_virtual_destructor_v<fesa::Load>);
|
||||
static_assert(!std::is_copy_constructible_v<fesa::StepDefinition>);
|
||||
static_assert(std::is_move_constructible_v<fesa::StepDefinition>);
|
||||
|
||||
fesa::Domain domain = MakeDomain();
|
||||
auto model = fesa::AnalysisModel::Create(domain);
|
||||
ASSERT_TRUE(model.HasValue());
|
||||
auto dofs = fesa::DofManager::Create(model.Value());
|
||||
ASSERT_TRUE(dofs.HasValue());
|
||||
const fesa::SourceTargetIndex target_index =
|
||||
fesa::SourceTargetIndex::FromDomain(domain);
|
||||
const fesa::SourceTargetResolver resolver{target_index};
|
||||
const fesa::LoadContext context{domain, dofs.Value(), resolver};
|
||||
const std::array<double, 6> components{1.0, -2.0, 0.0, 4.0, 0.0, -6.0};
|
||||
const fesa::ConcentratedNodalLoad load(
|
||||
{fesa::SourceEntityKind::kNode, "Part-1-1", "pair"}, components, 7U);
|
||||
|
||||
const auto result = load.ComputeContributions(context);
|
||||
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
ASSERT_EQ(result.Value().size(), 12U);
|
||||
for (std::size_t node = 0U; node < 2U; ++node) {
|
||||
for (std::size_t component = 0U; component < components.size();
|
||||
++component) {
|
||||
const auto& contribution =
|
||||
result.Value()[node * components.size() + component];
|
||||
EXPECT_EQ(contribution.source_order, 7U);
|
||||
EXPECT_EQ(contribution.full_dof_index,
|
||||
node * components.size() + component);
|
||||
EXPECT_DOUBLE_EQ(contribution.value, components[component]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Load, ConcentratedNodalLoadRejectsInvalidInputWithoutContributions) {
|
||||
fesa::Domain domain = MakeDomain();
|
||||
auto model = fesa::AnalysisModel::Create(domain);
|
||||
ASSERT_TRUE(model.HasValue());
|
||||
auto dofs = fesa::DofManager::Create(model.Value());
|
||||
ASSERT_TRUE(dofs.HasValue());
|
||||
const fesa::SourceTargetIndex target_index =
|
||||
fesa::SourceTargetIndex::FromDomain(domain);
|
||||
const fesa::SourceTargetResolver resolver{target_index};
|
||||
const fesa::LoadContext context{domain, dofs.Value(), resolver};
|
||||
|
||||
std::array<double, 6> nonfinite{};
|
||||
nonfinite[2U] = std::numeric_limits<double>::infinity();
|
||||
const fesa::ConcentratedNodalLoad invalid_value(
|
||||
{fesa::SourceEntityKind::kNode, "Part-1-1", "10"}, nonfinite, 0U);
|
||||
ExpectFailureCode(invalid_value.ComputeContributions(context),
|
||||
"nonfinite-load-value");
|
||||
|
||||
const fesa::ConcentratedNodalLoad invalid_target(
|
||||
{fesa::SourceEntityKind::kNode, "Part-1-1", "Missing"}, {}, 0U);
|
||||
ExpectFailureCode(invalid_target.ComputeContributions(context),
|
||||
"invalid-load-target");
|
||||
}
|
||||
@@ -191,15 +191,18 @@ TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) {
|
||||
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].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.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);
|
||||
ASSERT_EQ(domain.Steps()[0].ConcentratedLoads().Size(), 1U);
|
||||
EXPECT_DOUBLE_EQ(
|
||||
domain.Steps()[0].ConcentratedLoads()[0U].GlobalComponents()[2U],
|
||||
-1000.0);
|
||||
|
||||
ASSERT_EQ(domain.Warnings().size(), 1U);
|
||||
EXPECT_EQ(domain.Warnings()[0].code, "ignored-output-request");
|
||||
|
||||
Reference in New Issue
Block a user