feat(cpp-object-oriented-modular-refactoring): step 21 - domain-mapper-modules
This commit is contained in:
@@ -24,6 +24,7 @@ add_executable(
|
||||
unit/math/sparse_matrix_test.cpp
|
||||
unit/math/vector3_test.cpp
|
||||
unit/math/vector_test.cpp
|
||||
unit/io/abaqus/domain_mapping_components_test.cpp
|
||||
unit/io/abaqus/domain_mapper_test.cpp
|
||||
unit/io/abaqus/input_reader_test.cpp
|
||||
unit/io/abaqus/input_syntax_test.cpp
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/io/abaqus/domain_mapper.h"
|
||||
#include "fesa/io/abaqus/input_reader.h"
|
||||
#include "io/abaqus/domain_builder.h"
|
||||
#include "io/abaqus/domain_mapping_context.h"
|
||||
#include "io/abaqus/material_property_mapper.h"
|
||||
#include "io/abaqus/step_definition_mapper.h"
|
||||
#include "io/abaqus/topology_mapper.h"
|
||||
|
||||
namespace fesa::abaqus_internal {
|
||||
namespace {
|
||||
|
||||
class TemporaryComponentInput {
|
||||
public:
|
||||
TemporaryComponentInput(const std::string& stem, const std::string& content)
|
||||
: path_{std::filesystem::temp_directory_path() /
|
||||
("fesa-domain-components-" + stem + ".inp")} {
|
||||
std::ofstream stream{path_, std::ios::binary | std::ios::trunc};
|
||||
stream.write(content.data(), static_cast<std::streamsize>(content.size()));
|
||||
if (!stream) {
|
||||
throw std::runtime_error{"Unable to create mapper component fixture."};
|
||||
}
|
||||
}
|
||||
|
||||
~TemporaryComponentInput() {
|
||||
std::error_code error;
|
||||
std::filesystem::remove(path_, error);
|
||||
}
|
||||
|
||||
const std::filesystem::path& Path() const noexcept { return path_; }
|
||||
|
||||
private:
|
||||
std::filesystem::path path_;
|
||||
};
|
||||
|
||||
ParsedInput ParseComponentInput(const std::string& stem,
|
||||
const std::string& content) {
|
||||
const TemporaryComponentInput input{stem, content};
|
||||
auto parsed = AbaqusInputReader{}.Read(input.Path());
|
||||
if (!parsed.HasValue()) {
|
||||
throw std::runtime_error{"Unable to parse mapper component fixture."};
|
||||
}
|
||||
return std::move(parsed.Value());
|
||||
}
|
||||
|
||||
std::string ValidComponentDeck() {
|
||||
return R"inp(*Preprint
|
||||
*Part, name=BeamPart
|
||||
*Node
|
||||
1, 0., 0., 0.
|
||||
2, 1., 0., 0.
|
||||
*Element, type=B33
|
||||
1, 1, 2
|
||||
*Nset, nset=LocalRoot
|
||||
1
|
||||
*Elset, elset=BeamSet
|
||||
1
|
||||
*Beam General Section, elset=BeamSet, material=Steel, section=GENERAL
|
||||
1., 1., 0., 1., 1.
|
||||
0., 1., 0.
|
||||
*End Part
|
||||
*Assembly, name=Assembly
|
||||
*Instance, name=Beam-1, part=BeamPart
|
||||
*End Instance
|
||||
*Nset, nset=Root, instance=Beam-1
|
||||
1
|
||||
*Nset, nset=Tip, instance=Beam-1
|
||||
2
|
||||
*End Assembly
|
||||
*Material, name=Steel
|
||||
*Elastic
|
||||
100., 0.25
|
||||
*Boundary
|
||||
Root, 1, 6
|
||||
*Step, name=Load, nlgeom=NO
|
||||
*Static
|
||||
0.1, 1., 0.01, 1.
|
||||
*Boundary
|
||||
Tip, 1, 1, 0.125
|
||||
*Cload
|
||||
Tip, 2, -1.
|
||||
*Output, field
|
||||
*Node Output
|
||||
U
|
||||
*End Step
|
||||
)inp";
|
||||
}
|
||||
|
||||
std::string ReplaceOnce(std::string text, const std::string& from,
|
||||
const std::string& to) {
|
||||
const std::size_t position = text.find(from);
|
||||
if (position == std::string::npos) {
|
||||
throw std::logic_error{"Mapper component mutation source was not found."};
|
||||
}
|
||||
text.replace(position, from.size(), to);
|
||||
return text;
|
||||
}
|
||||
|
||||
void ExpectSameDiagnostic(const Diagnostic& actual,
|
||||
const Diagnostic& expected) {
|
||||
EXPECT_EQ(actual.severity, expected.severity);
|
||||
EXPECT_EQ(actual.code, expected.code);
|
||||
EXPECT_EQ(actual.location.file, expected.location.file);
|
||||
EXPECT_EQ(actual.location.line, expected.location.line);
|
||||
EXPECT_EQ(actual.keyword, expected.keyword);
|
||||
EXPECT_EQ(actual.entity_identity, expected.entity_identity);
|
||||
EXPECT_EQ(actual.message, expected.message);
|
||||
}
|
||||
|
||||
TEST(DomainMappingComponents,
|
||||
CModule001BuildsStableCandidatesBeforeAtomicDomainCommit) {
|
||||
ParsedInput input = ParseComponentInput("valid", ValidComponentDeck());
|
||||
DomainMappingContext context{input};
|
||||
TopologyMapper topology_mapper;
|
||||
MaterialPropertyMapper material_property_mapper;
|
||||
StepDefinitionMapper step_definition_mapper;
|
||||
|
||||
ASSERT_TRUE(topology_mapper.Map(context).IsOk());
|
||||
DomainMappingCandidateSummary summary = context.CandidateSummary();
|
||||
EXPECT_EQ(summary.part_count, 1U);
|
||||
EXPECT_EQ(summary.node_count, 2U);
|
||||
EXPECT_EQ(summary.element_count, 1U);
|
||||
EXPECT_EQ(summary.instance_count, 1U);
|
||||
EXPECT_EQ(summary.node_set_count, 3U);
|
||||
EXPECT_EQ(summary.element_set_count, 1U);
|
||||
EXPECT_EQ(summary.material_count, 1U);
|
||||
EXPECT_EQ(summary.beam_section_count, 1U);
|
||||
EXPECT_EQ(summary.shell_section_count, 0U);
|
||||
EXPECT_EQ(summary.model_boundary_count, 1U);
|
||||
EXPECT_EQ(summary.step_boundary_count, 1U);
|
||||
EXPECT_EQ(summary.step_load_count, 1U);
|
||||
EXPECT_TRUE(summary.has_static_step);
|
||||
|
||||
ASSERT_TRUE(material_property_mapper.Map(context).IsOk());
|
||||
ASSERT_TRUE(topology_mapper.Finalize(context).IsOk());
|
||||
ASSERT_TRUE(step_definition_mapper.Map(context).IsOk());
|
||||
auto result = DomainBuilder{}.Build(context);
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
|
||||
const Domain& domain = result.Value();
|
||||
EXPECT_EQ(domain.Nodes().size(), 2U);
|
||||
EXPECT_EQ(domain.Elements().Size(), 1U);
|
||||
EXPECT_EQ(domain.Materials().Size(), 1U);
|
||||
EXPECT_EQ(domain.Properties().Size(), 1U);
|
||||
ASSERT_EQ(domain.Steps().Size(), 1U);
|
||||
EXPECT_EQ(domain.Steps()[0].BoundaryConditions().size(), 7U);
|
||||
EXPECT_EQ(domain.Steps()[0].Loads().size(), 1U);
|
||||
ASSERT_EQ(domain.Warnings().size(), 3U);
|
||||
EXPECT_EQ(domain.Warnings()[0].keyword, "PREPRINT");
|
||||
EXPECT_EQ(domain.Warnings()[1].keyword, "OUTPUT");
|
||||
EXPECT_EQ(domain.Warnings()[2].keyword, "NODE OUTPUT");
|
||||
}
|
||||
|
||||
TEST(DomainMappingComponents,
|
||||
CModule001PreservesExactFirstFailureThroughFacade) {
|
||||
ParsedInput input = ParseComponentInput(
|
||||
"invalid", "*Node\n1, 0., 0., 0.\n*Material, name=Steel\n");
|
||||
DomainMappingContext context{input};
|
||||
const Status component_status = TopologyMapper{}.Map(context);
|
||||
ASSERT_FALSE(component_status.IsOk());
|
||||
ASSERT_EQ(component_status.Diagnostics().size(), 1U);
|
||||
|
||||
const Diagnostic& component_diagnostic =
|
||||
component_status.Diagnostics().front();
|
||||
EXPECT_EQ(component_status.Category(), FailureCategory::kInput);
|
||||
EXPECT_EQ(component_diagnostic.severity, Severity::kError);
|
||||
EXPECT_EQ(component_diagnostic.code, "unsupported-keyword");
|
||||
EXPECT_EQ(component_diagnostic.location.line, 1U);
|
||||
EXPECT_EQ(component_diagnostic.keyword, "NODE");
|
||||
EXPECT_TRUE(component_diagnostic.entity_identity.empty());
|
||||
EXPECT_EQ(component_diagnostic.message,
|
||||
"The keyword is outside the approved Abaqus subset.");
|
||||
|
||||
auto facade_result = AbaqusDomainMapper{}.Map(input);
|
||||
ASSERT_FALSE(facade_result.HasValue());
|
||||
ASSERT_EQ(facade_result.GetStatus().Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(facade_result.GetStatus().Category(), component_status.Category());
|
||||
ExpectSameDiagnostic(facade_result.GetStatus().Diagnostics().front(),
|
||||
component_diagnostic);
|
||||
}
|
||||
|
||||
TEST(DomainMappingComponents,
|
||||
CModule001RejectsInvalidMaterialAtOwningComponent) {
|
||||
ParsedInput input = ParseComponentInput(
|
||||
"invalid-material",
|
||||
ReplaceOnce(ValidComponentDeck(), "100., 0.25", "-100., 0.25"));
|
||||
DomainMappingContext context{input};
|
||||
ASSERT_TRUE(TopologyMapper{}.Map(context).IsOk());
|
||||
|
||||
const Status status = MaterialPropertyMapper{}.Map(context);
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Category(), FailureCategory::kModel);
|
||||
const Diagnostic& diagnostic = status.Diagnostics().front();
|
||||
EXPECT_EQ(diagnostic.code, "invalid-beam-property");
|
||||
EXPECT_EQ(diagnostic.location.line, 26U);
|
||||
EXPECT_EQ(diagnostic.keyword, "ELASTIC");
|
||||
EXPECT_EQ(diagnostic.entity_identity, "Steel");
|
||||
EXPECT_EQ(diagnostic.message,
|
||||
"E and the derived G=E/(2*(1+nu)) must be positive.");
|
||||
}
|
||||
|
||||
TEST(DomainMappingComponents,
|
||||
CModule001RejectsUnresolvedLoadAtOwningComponent) {
|
||||
ParsedInput input = ParseComponentInput(
|
||||
"invalid-step",
|
||||
ReplaceOnce(ValidComponentDeck(), "Tip, 2, -1.", "Missing, 2, -1."));
|
||||
DomainMappingContext context{input};
|
||||
TopologyMapper topology_mapper;
|
||||
ASSERT_TRUE(topology_mapper.Map(context).IsOk());
|
||||
ASSERT_TRUE(MaterialPropertyMapper{}.Map(context).IsOk());
|
||||
ASSERT_TRUE(topology_mapper.Finalize(context).IsOk());
|
||||
|
||||
const Status status = StepDefinitionMapper{}.Map(context);
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Category(), FailureCategory::kInput);
|
||||
const Diagnostic& diagnostic = status.Diagnostics().front();
|
||||
EXPECT_EQ(diagnostic.code, "unresolved-reference");
|
||||
EXPECT_EQ(diagnostic.location.line, 35U);
|
||||
EXPECT_EQ(diagnostic.keyword, "CLOAD");
|
||||
EXPECT_EQ(diagnostic.entity_identity, "Missing");
|
||||
EXPECT_EQ(diagnostic.message,
|
||||
"The boundary or load target must resolve unambiguously to one "
|
||||
"node or one node set.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace fesa::abaqus_internal
|
||||
Reference in New Issue
Block a user