feat: map fixed-step HHT shell dynamics input
This commit is contained in:
@@ -660,7 +660,10 @@ TEST(InpDomainMapping, RejectsUnsupportedNonlinearProceduresAndMixedModels) {
|
||||
test_case.second);
|
||||
ASSERT_FALSE(result.HasValue());
|
||||
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kInput);
|
||||
ASSERT_NE(FindDiagnostic(result.GetStatus(), "unsupported-analysis-procedure"),
|
||||
ASSERT_NE(FindDiagnostic(result.GetStatus(),
|
||||
test_case.first == "dynamic"
|
||||
? "invalid-dynamic-data"
|
||||
: "unsupported-analysis-procedure"),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/io/abaqus/domain_mapper.h"
|
||||
#include "fesa/io/abaqus/input_reader.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class TemporaryInputFile {
|
||||
public:
|
||||
TemporaryInputFile(const std::string& stem, const std::string& content)
|
||||
: path_{std::filesystem::temp_directory_path() /
|
||||
("fesa-hht-domain-" + 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 dynamic input fixture."};
|
||||
}
|
||||
}
|
||||
|
||||
~TemporaryInputFile() {
|
||||
std::error_code error;
|
||||
std::filesystem::remove(path_, error);
|
||||
}
|
||||
|
||||
const std::filesystem::path& Path() const noexcept { return path_; }
|
||||
|
||||
private:
|
||||
std::filesystem::path path_;
|
||||
};
|
||||
|
||||
fesa::Result<fesa::Domain> MapText(const std::string& stem,
|
||||
const std::string& content) {
|
||||
const TemporaryInputFile input{stem, content};
|
||||
auto parsed = fesa::AbaqusInputReader{}.Read(input.Path());
|
||||
if (!parsed.HasValue()) {
|
||||
return fesa::Result<fesa::Domain>::Failure(parsed.GetStatus());
|
||||
}
|
||||
return fesa::AbaqusDomainMapper{}.Map(parsed.Value());
|
||||
}
|
||||
|
||||
const fesa::Diagnostic* FindDiagnostic(const fesa::Status& status,
|
||||
const std::string& code) {
|
||||
for (const auto& diagnostic : status.Diagnostics()) {
|
||||
if (diagnostic.code == code) {
|
||||
return &diagnostic;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string DynamicDeck() {
|
||||
return R"inp(*Preprint, echo=NO, model=NO, history=NO, contact=NO
|
||||
*Part, name=ShellPart
|
||||
*Node
|
||||
1, 0., 0., 0.
|
||||
2, 1., 0., 0.
|
||||
3, 1., 1., 0.
|
||||
4, 0., 1., 0.
|
||||
*Element, type=S4R
|
||||
1, 1, 2, 3, 4
|
||||
*Elset, elset=ShellSet
|
||||
1
|
||||
*Shell Section, elset=ShellSet, material=Steel
|
||||
0.5, 5
|
||||
*End Part
|
||||
*Assembly, name=Assembly
|
||||
*Instance, name=Shell-1, part=ShellPart
|
||||
*End Instance
|
||||
*Nset, nset=Root, instance=Shell-1
|
||||
1
|
||||
*Nset, nset=Tip, instance=Shell-1
|
||||
3
|
||||
*End Assembly
|
||||
*Material, name=Steel
|
||||
*Density
|
||||
2700.,
|
||||
*Elastic
|
||||
2.1e11, 0.3
|
||||
*Boundary
|
||||
Root, 1, 6
|
||||
*Step, name=Dynamic-Load, nlgeom=YES, inc=1000
|
||||
*Dynamic, direct
|
||||
0.1, 10.,
|
||||
*Cload
|
||||
Tip, 1, -1000.
|
||||
*Restart, write, frequency=0
|
||||
*Output, field, variable=PRESELECT, frequency=1
|
||||
*Output, history, variable=PRESELECT
|
||||
*End Step
|
||||
)inp";
|
||||
}
|
||||
|
||||
std::string ReplaceOnce(std::string text, const std::string& from,
|
||||
const std::string& to) {
|
||||
const auto position = text.find(from);
|
||||
if (position == std::string::npos) {
|
||||
throw std::logic_error{"Dynamic fixture mutation source was not found."};
|
||||
}
|
||||
text.replace(position, from.size(), to);
|
||||
return text;
|
||||
}
|
||||
|
||||
std::filesystem::path RepositoryRoot() {
|
||||
auto path = std::filesystem::path{__FILE__}.parent_path();
|
||||
for (int parent = 0; parent < 4; ++parent) {
|
||||
path = path.parent_path();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
TEST(HhtDynamicDomainMapping, MapsDirectScheduleDensityAndSourceOrder) {
|
||||
auto result = MapText("accepted", DynamicDeck());
|
||||
ASSERT_TRUE(result.HasValue())
|
||||
<< (result.HasValue() ? ""
|
||||
: result.GetStatus().Diagnostics().front().message);
|
||||
const fesa::Domain& domain = result.Value();
|
||||
|
||||
ASSERT_NE(domain.Dynamic(), nullptr);
|
||||
const auto& dynamic = *domain.Dynamic();
|
||||
EXPECT_EQ(dynamic.source_step_name_raw, "Dynamic-Load");
|
||||
EXPECT_EQ(dynamic.source_step_name_canonical, "DYNAMIC-LOAD");
|
||||
EXPECT_DOUBLE_EQ(dynamic.initial_increment, 0.1);
|
||||
EXPECT_DOUBLE_EQ(dynamic.time_period, 10.0);
|
||||
EXPECT_EQ(dynamic.increment_count, 100U);
|
||||
EXPECT_EQ(dynamic.maximum_increments, 1000U);
|
||||
EXPECT_DOUBLE_EQ(dynamic.alpha, -0.05);
|
||||
EXPECT_DOUBLE_EQ(dynamic.beta, 0.275625);
|
||||
EXPECT_DOUBLE_EQ(dynamic.gamma, 0.55);
|
||||
ASSERT_EQ(dynamic.boundaries.size(), 1U);
|
||||
ASSERT_EQ(dynamic.loads.size(), 1U);
|
||||
EXPECT_EQ(dynamic.loads[0U].dof, 1);
|
||||
|
||||
ASSERT_EQ(domain.Densities().size(), 1U);
|
||||
EXPECT_DOUBLE_EQ(domain.Densities()[0U].rho, 2700.0);
|
||||
ASSERT_EQ(domain.Steps().Size(), 1U);
|
||||
EXPECT_TRUE(domain.Steps()[0U].IsDynamic());
|
||||
EXPECT_FALSE(domain.Steps()[0U].IsNonlinearStatic());
|
||||
EXPECT_EQ(domain.Steps()[0U].Dynamic(), domain.Dynamic());
|
||||
ASSERT_EQ(domain.Warnings().size(), 4U);
|
||||
for (const auto& warning : domain.Warnings()) {
|
||||
EXPECT_EQ(warning.code, "ignored-output-request");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(HhtDynamicDomainMapping, MapsOriginalReferenceDeckWithoutMutation) {
|
||||
const auto input_path = RepositoryRoot() / "reference" /
|
||||
"shellNL-dyn-meshfine" / "shellNL-dyn-meshfine.inp";
|
||||
auto parsed = fesa::AbaqusInputReader{}.Read(input_path);
|
||||
ASSERT_TRUE(parsed.HasValue());
|
||||
auto result = fesa::AbaqusDomainMapper{}.Map(parsed.Value());
|
||||
ASSERT_TRUE(result.HasValue())
|
||||
<< (result.HasValue() ? ""
|
||||
: result.GetStatus().Diagnostics().front().message);
|
||||
ASSERT_NE(result.Value().Dynamic(), nullptr);
|
||||
EXPECT_EQ(result.Value().Dynamic()->increment_count, 100U);
|
||||
EXPECT_EQ(result.Value().Dynamic()->maximum_increments, 1000U);
|
||||
EXPECT_EQ(result.Value().Dynamic()->loads.size(), 3U);
|
||||
}
|
||||
|
||||
TEST(HhtDynamicDomainMapping, RejectsMalformedScheduleAndUnsupportedControls) {
|
||||
struct InvalidCase {
|
||||
std::string name;
|
||||
std::string deck;
|
||||
std::string diagnostic;
|
||||
};
|
||||
const std::string base = DynamicDeck();
|
||||
const std::vector<InvalidCase> cases{
|
||||
{"noninteger-grid", ReplaceOnce(base, "0.1, 10.,", "0.3, 10.,"),
|
||||
"invalid-dynamic-schedule"},
|
||||
{"increment-limit", ReplaceOnce(base, "inc=1000", "inc=99"),
|
||||
"invalid-dynamic-schedule"},
|
||||
{"nonpositive-data", ReplaceOnce(base, "0.1, 10.,", "0., 10.,"),
|
||||
"invalid-dynamic-data"},
|
||||
{"integrator-override",
|
||||
ReplaceOnce(base, "*Dynamic, direct", "*Dynamic, direct, alpha=-0.1"),
|
||||
"unsupported-dynamic-parameter"},
|
||||
{"rotational-load", ReplaceOnce(base, "Tip, 1, -1000.", "Tip, 4, 0."),
|
||||
"unsupported-dynamic-load"},
|
||||
{"nonzero-boundary", ReplaceOnce(base, "Root, 1, 6", "Root, 1, 6, 1."),
|
||||
"unsupported-dynamic-boundary"},
|
||||
{"amplitude-boundary",
|
||||
ReplaceOnce(base, "*Boundary\n", "*Boundary, amplitude=A\n"),
|
||||
"unsupported-dynamic-boundary"},
|
||||
{"missing-density", ReplaceOnce(base, "*Density\n2700.,\n", ""),
|
||||
"missing-material-density"},
|
||||
{"unresolved-load",
|
||||
ReplaceOnce(base, "Tip, 1, -1000.", "Missing, 1, -1000."),
|
||||
"unresolved-reference"}};
|
||||
|
||||
for (const auto& test_case : cases) {
|
||||
SCOPED_TRACE(test_case.name);
|
||||
auto result = MapText(test_case.name, test_case.deck);
|
||||
ASSERT_FALSE(result.HasValue());
|
||||
ASSERT_NE(FindDiagnostic(result.GetStatus(), test_case.diagnostic),
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user