237 lines
8.0 KiB
C++
237 lines
8.0 KiB
C++
#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
|