feat(linear-static-3d-euler-beam): step 12 - inp-domain-mapping
This commit is contained in:
@@ -238,3 +238,91 @@
|
|||||||
- handoff: backend-free `KeywordParameter`, `DataLine`, `KeywordBlock`,
|
- handoff: backend-free `KeywordParameter`, `DataLine`, `KeywordBlock`,
|
||||||
`ParsedInput`, and `AbaqusInputReader::read` with syntax/source provenance are
|
`ParsedInput`, and `AbaqusInputReader::read` with syntax/source provenance are
|
||||||
available to Step 12 semantic mapping.
|
available to Step 12 semantic mapping.
|
||||||
|
|
||||||
|
|
||||||
|
## Step 12 — inp-domain-mapping
|
||||||
|
|
||||||
|
- task_id: `TASK-12`
|
||||||
|
- status: `completed`
|
||||||
|
- changed_files: `include/fesa/io/abaqus/domain_mapper.hpp`,
|
||||||
|
`src/fesa/io/abaqus/domain_mapper.cpp`,
|
||||||
|
`tests/unit/io/abaqus/domain_mapper_test.cpp`,
|
||||||
|
`src/fesa/CMakeLists.txt`, `tests/CMakeLists.txt`,
|
||||||
|
`docs/implementation-plans/linear-static-3d-euler-beam-implementation-report.md`,
|
||||||
|
`phases/linear-static-3d-euler-beam/index.json`,
|
||||||
|
`.superpowers/sdd/linear-static-3d-euler-beam/task-12-report.md`
|
||||||
|
- requirement_ids: `FESA-REQ-LS3DEB-001`, `FESA-REQ-LS3DEB-002`,
|
||||||
|
`FESA-REQ-LS3DEB-005` through `FESA-REQ-LS3DEB-020`,
|
||||||
|
`FESA-REQ-LS3DEB-034`, `FESA-REQ-LS3DEB-044`
|
||||||
|
- test_ids: `T12-MAP-001`, `T12-MAP-002`, `T12-MAP-003`,
|
||||||
|
`T12-MAP-004`, `T12-MAP-005`
|
||||||
|
|
||||||
|
| stage | exact command | exit_code | expected_or_observed_result | evidence_tail |
|
||||||
|
| --- | --- | ---: | --- | --- |
|
||||||
|
| RED | `cmake --build .harness/build --config Debug --target fesa_tests`; `ctest --test-dir .harness/build -C Debug -R InpDomainMapping --output-on-failure` | 1 | All five planned tests were registered before production and the implementation-owned mapper API was absent | MSVC C1083 reported missing `fesa/io/abaqus/domain_mapper.hpp`; CTest then found no runnable mapper tests because the executable did not build |
|
||||||
|
| GREEN-build | `cmake --build .harness/build --config Debug --target fesa_tests` | 0 | The mapper, five semantic tests, solver library, and unit executable compile and link | `domain_mapper.cpp` and `domain_mapper_test.cpp` built without a FESA warning under `/W4 /WX` |
|
||||||
|
| GREEN-test | `ctest --test-dir .harness/build -C Debug -R InpDomainMapping --output-on-failure` | 0 | Exact supported inventory, deterministic expansion, no-op policy, negative portfolio, and DLOAD exclusion pass | 5/5 exact `InpDomainMapping` tests passed |
|
||||||
|
| VERIFY-configure | `cmake -S . -B .harness/build -A x64 -DFESA_GTEST_SOURCE_DIR=C:/git/googletest "-DMKL_DIR=C:/Program Files (x86)/Intel/oneAPI/mkl/2026.1/lib/cmake/mkl" "-DTBB_DIR=C:/Program Files (x86)/Intel/oneAPI/tbb/2023.1/lib/cmake/tbb" "-DHDF5_DIR=C:/Program Files/HDF_Group/HDF5/2.1.1/cmake"` | 0 | Approved MSVC x64 build tree regenerates with explicit dependencies | Windows SDK and oneMKL 2026.1 resolved; configure and generate completed |
|
||||||
|
| VERIFY-build | `cmake --build .harness/build --config Debug` | 0 | Full Debug build passes without a new FESA warning | `fesa_solver.lib` and `fesa_unit_tests.exe` built under the existing `/W4 /WX` policy |
|
||||||
|
| VERIFY-targeted | `ctest --test-dir .harness/build -C Debug -R InpDomainMapping --output-on-failure` | 0 | Focused Step 12 suite remains green after review corrections | 5/5 `InpDomainMapping` tests passed |
|
||||||
|
| VERIFY-discovery | `ctest --test-dir .harness/build -C Debug --show-only=json-v1` | 0 | CTest discovers the accumulated suite and all five exact mapper tests | 19 tests discovered with all five planned `InpDomainMapping` names and feature/unit labels |
|
||||||
|
| VERIFY-full | `ctest --test-dir .harness/build -C Debug --output-on-failure` | 0 | Full accumulated C++ suite has zero failures | 19/19 tests passed |
|
||||||
|
| VERIFY-dependency-direction | Backend, upward-I/O, core-upward, distributed-load-object, unapproved-`nu`-gate, and exact-test-count scans over Step 12 files | 0 | Public dependencies and approved scope remain intact | backend leaks 0; upward dependencies 0; core upward dependencies 0; distributed-load objects 0; unapproved `nu` gates 0; mapper tests 5 |
|
||||||
|
| VERIFY-diff | `git diff --check` plus trailing-whitespace scan over the three new files | 0 | Tracked and untracked Step 12 files have no whitespace errors | Git reported informational LF-to-CRLF notices only; new-file trailing whitespace matches 0 |
|
||||||
|
| VERIFY-reference | `git diff --exit-code -- reference/`; `git status --short -- reference/` | 0 | Approved legacy reference artifacts remain read-only and unchanged | Reference diff exit 0 and reference status empty |
|
||||||
|
|
||||||
|
- contract_checks: the mapper consumes syntax-only `ParsedInput` and returns
|
||||||
|
one complete immutable `Domain` or a categorized diagnostic. It enforces the
|
||||||
|
monotonic top-level/part/assembly/step grammar, B33 only, identity instances
|
||||||
|
only, exact inclusive set generation, case-insensitive references, stable
|
||||||
|
declaration/instance-order internal IDs, and `SourceEntityId
|
||||||
|
{instance_name, source_label}`. Direct node labels, part and assembly sets,
|
||||||
|
ambiguous multi-instance labels/sets, and direct-versus-set constraint
|
||||||
|
overlap are resolved before Domain success.
|
||||||
|
- contract_checks: material and section mapping uses exact `E,nu`,
|
||||||
|
`G=E/(2*(1+nu))`, `A,I11,I12,I22,J`, exact `I12=0`,
|
||||||
|
`Iy=I11`, `Iz=I22`, and `n1 -> local y`. It requires positive
|
||||||
|
`E,G,A,I11,I22,J` without adding a `nu < 0.5` gate. Nonfinite material,
|
||||||
|
section, and guide data are model failures. Overflow-safe scaled norms apply
|
||||||
|
the exact length and guide-vector inequalities; equality fails, values above
|
||||||
|
pass, and valid finite geometry near `1e308` is accepted without a fallback
|
||||||
|
axis.
|
||||||
|
- contract_checks: model-level and step-level boundaries and sole-step CLOAD
|
||||||
|
targets are validated, conflicting expanded node/DOF prescriptions fail, the
|
||||||
|
exact four positive finite `*STATIC` fields are retained, and the sole
|
||||||
|
canonical result identity is `Step-1`, frame 0. The exact warning no-op
|
||||||
|
allowlist produces stable `ignored-input-keyword` diagnostics without
|
||||||
|
changing semantic objects. B31, transforms, nested/dependent assembly
|
||||||
|
semantics, second step, NLGEOM, unknown keywords, and `*DLOAD` fail; no
|
||||||
|
distributed-load Domain object, B31 alias, multi-step path, or transform
|
||||||
|
fallback exists.
|
||||||
|
- generated_evidence: `.harness/build/src/fesa/Debug/fesa_solver.lib`,
|
||||||
|
`.harness/build/tests/Debug/fesa_unit_tests.exe`
|
||||||
|
- reference_diff: unchanged; the legacy cantilever input maps successfully
|
||||||
|
while exact bytes and modification time remain unchanged, and
|
||||||
|
`git diff --exit-code -- reference/` exits 0
|
||||||
|
- handoff: `AbaqusDomainMapper::map(const ParsedInput&)`, complete immutable
|
||||||
|
Domain objects, stable expanded node/element/set identities, validated
|
||||||
|
material/section indices, and the sole `StaticStepDefinition` are available
|
||||||
|
to Step 13 `AnalysisModel`.
|
||||||
|
|
||||||
|
### Step 12 Review Corrections
|
||||||
|
|
||||||
|
- A duplicate-`*ELASTIC` regression test first failed because a second
|
||||||
|
consecutive elasticity record lost the owning-material state. Preserving the
|
||||||
|
immediate material eligibility produced the exact `duplicate-entity`
|
||||||
|
diagnostic and restored targeted 5/5.
|
||||||
|
- Ten monotonic-order regressions first failed at the accepted late-`*PART`
|
||||||
|
case. Explicit phase state now rejects late model data, assembly sets before
|
||||||
|
or followed by instances, element-before-node, incomplete block closure, and
|
||||||
|
pre-`*STATIC` or out-of-order history data with
|
||||||
|
`invalid-keyword-location`.
|
||||||
|
- Review edge tests then failed for valid `1e308` geometry and for nonfinite
|
||||||
|
elasticity being classified as input. Context-specific model parsing,
|
||||||
|
overflow-safe scaled geometry comparisons, active beam-section context,
|
||||||
|
exact GENERATE landing, direct/ambiguous target resolution, and exact
|
||||||
|
threshold cases were added. The corrected focused suite passes 5/5 and the
|
||||||
|
full accumulated suite passes 19/19.
|
||||||
|
- Focused re-review returned Critical 0 and Important 0. Its sole remaining
|
||||||
|
minor requested an explicit negative transverse-shear context assertion;
|
||||||
|
that table case was added and passes without a production change.
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fesa/core/status.hpp"
|
||||||
|
#include "fesa/io/abaqus/input_syntax.hpp"
|
||||||
|
#include "fesa/model/domain.hpp"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
// Converts syntax-only blocks into the approved immutable B33 semantic model.
|
||||||
|
class AbaqusDomainMapper {
|
||||||
|
public:
|
||||||
|
Result<Domain> map(const ParsedInput& input) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
@@ -4,6 +4,7 @@ add_library(
|
|||||||
build_info.cpp
|
build_info.cpp
|
||||||
core/diagnostic.cpp
|
core/diagnostic.cpp
|
||||||
core/status.cpp
|
core/status.cpp
|
||||||
|
io/abaqus/domain_mapper.cpp
|
||||||
io/abaqus/input_reader.cpp
|
io/abaqus/input_reader.cpp
|
||||||
math/matrix.cpp
|
math/matrix.cpp
|
||||||
math/vector.cpp
|
math/vector.cpp
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ add_executable(
|
|||||||
unit/core/status_test.cpp
|
unit/core/status_test.cpp
|
||||||
unit/math/matrix_test.cpp
|
unit/math/matrix_test.cpp
|
||||||
unit/math/vector_test.cpp
|
unit/math/vector_test.cpp
|
||||||
|
unit/io/abaqus/domain_mapper_test.cpp
|
||||||
unit/io/abaqus/input_reader_test.cpp
|
unit/io/abaqus/input_reader_test.cpp
|
||||||
unit/io/abaqus/input_syntax_test.cpp
|
unit/io/abaqus/input_syntax_test.cpp
|
||||||
unit/model/domain_test.cpp
|
unit/model/domain_test.cpp
|
||||||
|
|||||||
@@ -0,0 +1,590 @@
|
|||||||
|
#include "fesa/io/abaqus/domain_mapper.hpp"
|
||||||
|
#include "fesa/io/abaqus/input_reader.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class TemporaryInputFile {
|
||||||
|
public:
|
||||||
|
TemporaryInputFile(const std::string& stem, const std::string& content)
|
||||||
|
: path_{std::filesystem::temp_directory_path() /
|
||||||
|
("fesa-domain-mapper-" + 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 domain mapper 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.status());
|
||||||
|
}
|
||||||
|
return fesa::AbaqusDomainMapper{}.map(parsed.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string readExactBytes(const std::filesystem::path& path) {
|
||||||
|
std::ifstream stream{path, std::ios::binary};
|
||||||
|
if (!stream) {
|
||||||
|
throw std::runtime_error{"Unable to read legacy mapper fixture."};
|
||||||
|
}
|
||||||
|
return std::string{
|
||||||
|
std::istreambuf_iterator<char>{stream},
|
||||||
|
std::istreambuf_iterator<char>{}};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fesa::Diagnostic* findDiagnostic(
|
||||||
|
const fesa::Status& status,
|
||||||
|
const std::string& code) {
|
||||||
|
const auto found = std::find_if(
|
||||||
|
status.diagnostics().begin(),
|
||||||
|
status.diagnostics().end(),
|
||||||
|
[&code](const fesa::Diagnostic& diagnostic) {
|
||||||
|
return diagnostic.code == code;
|
||||||
|
});
|
||||||
|
return found == status.diagnostics().end() ? nullptr : &*found;
|
||||||
|
}
|
||||||
|
|
||||||
|
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{"Mapper test mutation source was not found."};
|
||||||
|
}
|
||||||
|
text.replace(position, from.size(), to);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string minimalDeck() {
|
||||||
|
return R"inp(*Part, name=BeamPart
|
||||||
|
*Node
|
||||||
|
1, 0., 0., 0.
|
||||||
|
2, 1., 0., 0.
|
||||||
|
*Element, type=B33
|
||||||
|
1, 1, 2
|
||||||
|
*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.
|
||||||
|
*Cload
|
||||||
|
Tip, 2, -1.
|
||||||
|
*End Step
|
||||||
|
)inp";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string supportedInventoryDeck(bool includeNoOps) {
|
||||||
|
const std::string preprint = includeNoOps
|
||||||
|
? "*Preprint, echo=NO, model=NO, history=NO, contact=NO\n"
|
||||||
|
: "";
|
||||||
|
const std::string shear = includeNoOps
|
||||||
|
? "*Transverse Shear Stiffness\n1., 2., 3.\n"
|
||||||
|
: "";
|
||||||
|
const std::string outputs = includeNoOps
|
||||||
|
? R"inp(*Restart, write, frequency=0
|
||||||
|
*Output, field
|
||||||
|
*Node Output
|
||||||
|
U, RF
|
||||||
|
*Element Output, directions=YES
|
||||||
|
S, SF
|
||||||
|
*Contact Output, variable=PRESELECT
|
||||||
|
*Output, history, variable=PRESELECT
|
||||||
|
)inp"
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return std::string{R"inp(*hEaDiNg
|
||||||
|
Every supported keyword
|
||||||
|
)inp"} + preprint + R"inp(*pArT, NaMe=BeamPart
|
||||||
|
*nOdE
|
||||||
|
0001, 0., 0., 0.
|
||||||
|
0002, 2., 0., 0.
|
||||||
|
*eLeMeNt, TyPe=b33
|
||||||
|
0007, 0001, 0002
|
||||||
|
*nSeT, NsEt=RootLocal
|
||||||
|
0001
|
||||||
|
*Nset, nset=AllLocal, generate
|
||||||
|
1, 2, 1
|
||||||
|
*Elset, elset=BeamExplicit
|
||||||
|
0007
|
||||||
|
*eLsEt, ElSeT=BeamAll, GeNeRaTe
|
||||||
|
7, 7, 1
|
||||||
|
*Beam General Section, ELSET=beamall, MATERIAL=steel, SECTION=general
|
||||||
|
2., 3., 0., 4., 5.
|
||||||
|
0., 1., 0.
|
||||||
|
*Section Points
|
||||||
|
-0.5, 0.25
|
||||||
|
0.5, -0.25
|
||||||
|
)inp" + shear + R"inp(*eNd PaRt
|
||||||
|
*aSsEmBlY, name=Assembly
|
||||||
|
*iNsTaNcE, NAME=Beam-1, PART=beampart
|
||||||
|
*eNd InStAnCe
|
||||||
|
*Nset, nset=RootAssembly, instance=beam-1
|
||||||
|
0001
|
||||||
|
*Elset, elset=BeamAssembly, instance=BEAM-1
|
||||||
|
0007
|
||||||
|
*eNd AsSeMbLy
|
||||||
|
*mAtErIaL, NaMe=Steel
|
||||||
|
*eLaStIc
|
||||||
|
210000., 0.75
|
||||||
|
*bOuNdArY
|
||||||
|
rootassembly, 1, 1, 0.125
|
||||||
|
*sTeP, name=User-Spelling, nLgEoM=no
|
||||||
|
*sTaTiC
|
||||||
|
0.25, 1.5, 0.01, 1.5
|
||||||
|
*Boundary
|
||||||
|
RootAssembly, 2, 2
|
||||||
|
*cLoAd
|
||||||
|
RootAssembly, 6, -12.5
|
||||||
|
)inp" + outputs + "*eNd StEp\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
|
||||||
|
auto result = mapText("supported-inventory", supportedInventoryDeck(true));
|
||||||
|
ASSERT_TRUE(result.hasValue());
|
||||||
|
const fesa::Domain& domain = result.value();
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.nodes().size(), 2U);
|
||||||
|
EXPECT_EQ(domain.nodes()[0].sourceId.instanceName, "Beam-1");
|
||||||
|
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabel, 1);
|
||||||
|
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabelText, "0001");
|
||||||
|
EXPECT_EQ(domain.nodes()[1].sourceId.sourceLabelText, "0002");
|
||||||
|
EXPECT_DOUBLE_EQ(domain.nodes()[1].coordinates[0], 2.0);
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.elements().size(), 1U);
|
||||||
|
EXPECT_EQ(domain.elements()[0].sourceId.sourceLabelText, "0007");
|
||||||
|
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U);
|
||||||
|
EXPECT_EQ(domain.elements()[0].nodeIndices[1], 1U);
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.materials().size(), 1U);
|
||||||
|
EXPECT_EQ(domain.materials()[0].name, "Steel");
|
||||||
|
EXPECT_DOUBLE_EQ(domain.materials()[0].youngsModulus, 210000.0);
|
||||||
|
EXPECT_DOUBLE_EQ(domain.materials()[0].poissonRatio, 0.75);
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.sections().size(), 1U);
|
||||||
|
const auto& section = domain.sections()[0];
|
||||||
|
EXPECT_DOUBLE_EQ(section.area, 2.0);
|
||||||
|
EXPECT_DOUBLE_EQ(section.i11, 3.0);
|
||||||
|
EXPECT_DOUBLE_EQ(section.i12, 0.0);
|
||||||
|
EXPECT_DOUBLE_EQ(section.i22, 4.0);
|
||||||
|
EXPECT_DOUBLE_EQ(section.torsionalConstant, 5.0);
|
||||||
|
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.5, 0.25}, {0.5, -0.25}}));
|
||||||
|
EXPECT_EQ(domain.elements()[0].materialIndex, 0U);
|
||||||
|
EXPECT_EQ(domain.elements()[0].sectionIndex, 0U);
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.steps().size(), 1U);
|
||||||
|
const auto& step = domain.steps()[0];
|
||||||
|
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].firstDof, 1);
|
||||||
|
EXPECT_DOUBLE_EQ(step.boundaries[0].value, 0.125);
|
||||||
|
EXPECT_EQ(step.boundaries[1].lastDof, 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(domain.warnings().size(), 8U);
|
||||||
|
|
||||||
|
const auto legacyPath = repositoryRoot() / "reference" /
|
||||||
|
"cantilever beam" / "cantilever beam.inp";
|
||||||
|
const auto bytesBefore = readExactBytes(legacyPath);
|
||||||
|
const auto timestampBefore = std::filesystem::last_write_time(legacyPath);
|
||||||
|
auto parsedLegacy = fesa::AbaqusInputReader{}.read(legacyPath);
|
||||||
|
ASSERT_TRUE(parsedLegacy.hasValue());
|
||||||
|
auto legacy = fesa::AbaqusDomainMapper{}.map(parsedLegacy.value());
|
||||||
|
ASSERT_TRUE(legacy.hasValue());
|
||||||
|
EXPECT_EQ(legacy.value().nodes().size(), 11U);
|
||||||
|
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().warnings().size(), 7U);
|
||||||
|
EXPECT_EQ(readExactBytes(legacyPath), bytesBefore);
|
||||||
|
EXPECT_EQ(std::filesystem::last_write_time(legacyPath), timestampBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(InpDomainMapping, ExpandsSetsAndMultipleIdentityInstancesDeterministically) {
|
||||||
|
const std::string deck = R"inp(*Part, name=P
|
||||||
|
*Node
|
||||||
|
10, 0., 0., 0.
|
||||||
|
20, 1., 0., 0.
|
||||||
|
*Element, type=B33
|
||||||
|
30, 10, 20
|
||||||
|
*Nset, nset=Ends, generate
|
||||||
|
10, 20, 10
|
||||||
|
*Elset, elset=AllElements
|
||||||
|
30
|
||||||
|
*Beam General Section, elset=AllElements, material=M, section=GENERAL
|
||||||
|
1., 2., 0., 3., 4.
|
||||||
|
0., 1., 0.
|
||||||
|
*End Part
|
||||||
|
*Assembly, name=A
|
||||||
|
*Instance, name=First, part=P
|
||||||
|
*End Instance
|
||||||
|
*Instance, name=Second, part=P
|
||||||
|
*End Instance
|
||||||
|
*Nset, nset=OnlySecond, instance=Second
|
||||||
|
20
|
||||||
|
*Elset, elset=OnlySecondBeam, instance=Second
|
||||||
|
30
|
||||||
|
*End Assembly
|
||||||
|
*Material, name=M
|
||||||
|
*Elastic
|
||||||
|
1000., 0.25
|
||||||
|
*Step
|
||||||
|
*Static
|
||||||
|
1., 1., 1., 1.
|
||||||
|
*Boundary
|
||||||
|
OnlySecond, 1, 1
|
||||||
|
*Cload
|
||||||
|
OnlySecond, 2, 5.
|
||||||
|
*End Step
|
||||||
|
)inp";
|
||||||
|
|
||||||
|
auto result = mapText("multiple-instances", deck);
|
||||||
|
ASSERT_TRUE(result.hasValue());
|
||||||
|
const fesa::Domain& domain = result.value();
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.nodes().size(), 4U);
|
||||||
|
EXPECT_EQ(domain.nodes()[0].sourceId.instanceName, "First");
|
||||||
|
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabel, 10);
|
||||||
|
EXPECT_EQ(domain.nodes()[1].sourceId.instanceName, "First");
|
||||||
|
EXPECT_EQ(domain.nodes()[1].sourceId.sourceLabel, 20);
|
||||||
|
EXPECT_EQ(domain.nodes()[2].sourceId.instanceName, "Second");
|
||||||
|
EXPECT_EQ(domain.nodes()[2].sourceId.sourceLabel, 10);
|
||||||
|
EXPECT_EQ(domain.nodes()[3].sourceId.instanceName, "Second");
|
||||||
|
EXPECT_EQ(domain.nodes()[3].sourceId.sourceLabel, 20);
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.elements().size(), 2U);
|
||||||
|
EXPECT_EQ(domain.elements()[0].sourceId.instanceName, "First");
|
||||||
|
EXPECT_EQ(domain.elements()[0].nodeIndices,
|
||||||
|
(std::array<fesa::EntityIndex, 2>{0U, 1U}));
|
||||||
|
EXPECT_EQ(domain.elements()[1].sourceId.instanceName, "Second");
|
||||||
|
EXPECT_EQ(domain.elements()[1].nodeIndices,
|
||||||
|
(std::array<fesa::EntityIndex, 2>{2U, 3U}));
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.nodeSets().size(), 3U);
|
||||||
|
EXPECT_EQ(domain.nodeSets()[0].name, "Ends");
|
||||||
|
EXPECT_EQ(domain.nodeSets()[0].instanceName, std::optional<std::string>{"First"});
|
||||||
|
EXPECT_EQ(domain.nodeSets()[0].nodeIndices,
|
||||||
|
(std::vector<fesa::EntityIndex>{0U, 1U}));
|
||||||
|
EXPECT_EQ(domain.nodeSets()[1].instanceName, std::optional<std::string>{"Second"});
|
||||||
|
EXPECT_EQ(domain.nodeSets()[1].nodeIndices,
|
||||||
|
(std::vector<fesa::EntityIndex>{2U, 3U}));
|
||||||
|
EXPECT_EQ(domain.nodeSets()[2].name, "OnlySecond");
|
||||||
|
EXPECT_EQ(domain.nodeSets()[2].nodeIndices,
|
||||||
|
(std::vector<fesa::EntityIndex>{3U}));
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.elementSets().size(), 3U);
|
||||||
|
EXPECT_EQ(domain.elementSets()[0].instanceName,
|
||||||
|
std::optional<std::string>{"First"});
|
||||||
|
EXPECT_EQ(domain.elementSets()[0].elementIndices,
|
||||||
|
(std::vector<fesa::EntityIndex>{0U}));
|
||||||
|
EXPECT_EQ(domain.elementSets()[1].instanceName,
|
||||||
|
std::optional<std::string>{"Second"});
|
||||||
|
EXPECT_EQ(domain.elementSets()[1].elementIndices,
|
||||||
|
(std::vector<fesa::EntityIndex>{1U}));
|
||||||
|
EXPECT_EQ(domain.elementSets()[2].name, "OnlySecondBeam");
|
||||||
|
EXPECT_EQ(domain.elementSets()[2].elementIndices,
|
||||||
|
(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");
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
auto aboveThresholds = mapText(
|
||||||
|
"above-geometry-thresholds",
|
||||||
|
replaceOnce(
|
||||||
|
replaceOnce(minimalDeck(), "2, 1., 0., 0.", "2, 2e-12, 0., 0."),
|
||||||
|
"0., 1., 0.",
|
||||||
|
"1., 2e-12, 0."));
|
||||||
|
ASSERT_TRUE(aboveThresholds.hasValue());
|
||||||
|
|
||||||
|
auto largeFinite = mapText(
|
||||||
|
"large-finite-geometry",
|
||||||
|
replaceOnce(
|
||||||
|
replaceOnce(
|
||||||
|
replaceOnce(minimalDeck(), "1, 0., 0., 0.", "1, 1e308, 0., 0."),
|
||||||
|
"2, 1., 0., 0.",
|
||||||
|
"2, 1e308, 1e297, 0."),
|
||||||
|
"0., 1., 0.",
|
||||||
|
"1e308, 0., 0."));
|
||||||
|
ASSERT_TRUE(largeFinite.hasValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(InpDomainMapping, NoOpAllowlistWarnsWithoutSemanticEffect) {
|
||||||
|
auto plain = mapText("without-no-ops", supportedInventoryDeck(false));
|
||||||
|
auto withNoOps = mapText("with-no-ops", supportedInventoryDeck(true));
|
||||||
|
ASSERT_TRUE(plain.hasValue());
|
||||||
|
ASSERT_TRUE(withNoOps.hasValue());
|
||||||
|
|
||||||
|
EXPECT_TRUE(plain.value().warnings().empty());
|
||||||
|
ASSERT_EQ(withNoOps.value().warnings().size(), 8U);
|
||||||
|
EXPECT_EQ(withNoOps.value().warnings()[0].code, "ignored-input-keyword");
|
||||||
|
EXPECT_EQ(withNoOps.value().warnings()[0].keyword, "PREPRINT");
|
||||||
|
EXPECT_EQ(withNoOps.value().warnings()[1].keyword,
|
||||||
|
"TRANSVERSE SHEAR STIFFNESS");
|
||||||
|
EXPECT_EQ(withNoOps.value().warnings()[2].keyword, "RESTART");
|
||||||
|
EXPECT_EQ(withNoOps.value().warnings()[3].keyword, "OUTPUT");
|
||||||
|
EXPECT_EQ(withNoOps.value().warnings()[4].keyword, "NODE OUTPUT");
|
||||||
|
EXPECT_EQ(withNoOps.value().warnings()[5].keyword, "ELEMENT OUTPUT");
|
||||||
|
EXPECT_EQ(withNoOps.value().warnings()[6].keyword, "CONTACT OUTPUT");
|
||||||
|
EXPECT_EQ(withNoOps.value().warnings()[7].keyword, "OUTPUT");
|
||||||
|
for (const auto& warning : withNoOps.value().warnings()) {
|
||||||
|
EXPECT_EQ(warning.severity, fesa::Severity::warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(withNoOps.value().nodes().size(), plain.value().nodes().size());
|
||||||
|
EXPECT_EQ(withNoOps.value().elements().size(), plain.value().elements().size());
|
||||||
|
EXPECT_EQ(withNoOps.value().materials().size(), plain.value().materials().size());
|
||||||
|
EXPECT_EQ(withNoOps.value().sections().size(), plain.value().sections().size());
|
||||||
|
EXPECT_EQ(withNoOps.value().nodeSets().size(), plain.value().nodeSets().size());
|
||||||
|
EXPECT_EQ(withNoOps.value().elementSets().size(), plain.value().elementSets().size());
|
||||||
|
EXPECT_EQ(withNoOps.value().steps().size(), plain.value().steps().size());
|
||||||
|
EXPECT_EQ(withNoOps.value().steps()[0].boundaries.size(),
|
||||||
|
plain.value().steps()[0].boundaries.size());
|
||||||
|
EXPECT_EQ(withNoOps.value().steps()[0].loads.size(),
|
||||||
|
plain.value().steps()[0].loads.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(InpDomainMapping, RejectsUnsupportedAndInvalidPortfolio) {
|
||||||
|
struct InvalidCase {
|
||||||
|
std::string name;
|
||||||
|
std::string deck;
|
||||||
|
std::string expectedCode;
|
||||||
|
fesa::FailureCategory category;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string base = minimalDeck();
|
||||||
|
const std::vector<InvalidCase> cases{
|
||||||
|
{"b31", replaceOnce(base, "type=B33", "type=B31"),
|
||||||
|
"unsupported-element-formulation", fesa::FailureCategory::input},
|
||||||
|
{"transform", replaceOnce(base, "*End Instance\n", "1., 0., 0.\n*End Instance\n"),
|
||||||
|
"unsupported-instance-transform", fesa::FailureCategory::input},
|
||||||
|
{"nested-assembly", replaceOnce(base, "*End Assembly\n", "*Assembly, name=Nested\n*End Assembly\n*End Assembly\n"),
|
||||||
|
"unsupported-nested-assembly", fesa::FailureCategory::input},
|
||||||
|
{"multiple-step", base + "*Step\n*Static\n1., 1., 1., 1.\n*End Step\n",
|
||||||
|
"unsupported-multiple-step", fesa::FailureCategory::input},
|
||||||
|
{"late-part", replaceOnce(base, "*End Assembly\n*Material", "*End Assembly\n*Part, name=Late\n*End Part\n*Material"),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"material-before-assembly", replaceOnce(base, "*Assembly, name=Assembly", "*Material, name=Early\n*Elastic\n50., 0.2\n*Assembly, name=Assembly"),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"material-after-model-boundary", replaceOnce(base,
|
||||||
|
"*Material, name=Steel\n*Elastic\n100., 0.25\n*Boundary\nRoot, 1, 6",
|
||||||
|
"*Boundary\nRoot, 1, 6\n*Material, name=Steel\n*Elastic\n100., 0.25"),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"keyword-after-step", base + "*Preprint, echo=NO\n",
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"assembly-instance-after-set", replaceOnce(base,
|
||||||
|
"*Instance, name=Beam-1, part=BeamPart\n*End Instance\n*Nset, nset=Root, instance=Beam-1\n1",
|
||||||
|
"*Nset, nset=Root, instance=Beam-1\n1\n*Instance, name=Beam-1, part=BeamPart\n*End Instance"),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"element-before-node", replaceOnce(base,
|
||||||
|
"*Node\n1, 0., 0., 0.\n2, 1., 0., 0.\n*Element, type=B33\n1, 1, 2",
|
||||||
|
"*Element, type=B33\n1, 1, 2\n*Node\n1, 0., 0., 0.\n2, 1., 0., 0."),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"shear-before-section-context", replaceOnce(base,
|
||||||
|
"*Beam General Section",
|
||||||
|
"*Transverse Shear Stiffness\n1., 2., 3.\n*Beam General Section"),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"incomplete-part", replaceOnce(base,
|
||||||
|
base.substr(base.find("*Part"), base.find("*End Part") + std::string{"*End Part\n"}.size() - base.find("*Part")),
|
||||||
|
"*Part, name=BeamPart\n*End Part\n"),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"empty-assembly", replaceOnce(base,
|
||||||
|
base.substr(base.find("*Assembly"), base.find("*End Assembly") + std::string{"*End Assembly\n"}.size() - base.find("*Assembly")),
|
||||||
|
"*Assembly, name=Assembly\n*End Assembly\n"),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"cload-before-static", replaceOnce(base,
|
||||||
|
"*Static\n0.1, 1., 0.01, 1.\n*Cload\nTip, 2, -1.",
|
||||||
|
"*Cload\nTip, 2, -1.\n*Static\n0.1, 1., 0.01, 1."),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"static-after-boundary", replaceOnce(
|
||||||
|
replaceOnce(base, "*Boundary\nRoot, 1, 6\n*Step", "*Step"),
|
||||||
|
"*Static\n0.1, 1., 0.01, 1.",
|
||||||
|
"*Boundary\nRoot, 1, 6\n*Static\n0.1, 1., 0.01, 1."),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"boundary-after-cload", replaceOnce(
|
||||||
|
replaceOnce(base, "*Boundary\nRoot, 1, 6\n*Step", "*Step"),
|
||||||
|
"Tip, 2, -1.\n*End Step",
|
||||||
|
"Tip, 2, -1.\n*Boundary\nRoot, 1, 6\n*End Step"),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"cload-after-no-op", replaceOnce(base, "*Cload", "*Restart, write, frequency=0\n*Cload"),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"step-without-static", replaceOnce(base,
|
||||||
|
"*Static\n0.1, 1., 0.01, 1.\n*Cload\nTip, 2, -1.\n*End Step",
|
||||||
|
"*End Step"),
|
||||||
|
"invalid-keyword-location", fesa::FailureCategory::input},
|
||||||
|
{"dependent-instance", replaceOnce(base, "part=BeamPart", "part=BeamPart, dependent=YES"),
|
||||||
|
"unsupported-instance-mesh-semantics", fesa::FailureCategory::input},
|
||||||
|
{"coupled-section", replaceOnce(base, "1., 1., 0., 1., 1.", "1., 1., 0.5, 1., 1."),
|
||||||
|
"unsupported-coupled-section", fesa::FailureCategory::model},
|
||||||
|
{"zero-length", replaceOnce(base, "2, 1., 0., 0.", "2, 0., 0., 0."),
|
||||||
|
"invalid-beam-length", fesa::FailureCategory::model},
|
||||||
|
{"parallel-guide", replaceOnce(base, "0., 1., 0.", "1., 0., 0."),
|
||||||
|
"invalid-beam-guide-vector", fesa::FailureCategory::model},
|
||||||
|
{"nonpositive-area", replaceOnce(base, "1., 1., 0., 1., 1.", "0., 1., 0., 1., 1."),
|
||||||
|
"invalid-beam-property", fesa::FailureCategory::model},
|
||||||
|
{"nonpositive-derived-shear", replaceOnce(base, "100., 0.25", "100., -1.25"),
|
||||||
|
"invalid-beam-property", fesa::FailureCategory::model},
|
||||||
|
{"nonfinite-elastic", replaceOnce(base, "100., 0.25", "inf, 0.25"),
|
||||||
|
"invalid-beam-property", fesa::FailureCategory::model},
|
||||||
|
{"nonfinite-section-property", replaceOnce(base, "1., 1., 0., 1., 1.", "nan, 1., 0., 1., 1."),
|
||||||
|
"invalid-beam-property", fesa::FailureCategory::model},
|
||||||
|
{"nonfinite-guide", replaceOnce(base, "0., 1., 0.", "0., inf, 0."),
|
||||||
|
"invalid-beam-guide-vector", fesa::FailureCategory::model},
|
||||||
|
{"length-at-threshold", replaceOnce(base, "2, 1., 0., 0.", "2, 1e-12, 0., 0."),
|
||||||
|
"invalid-beam-length", fesa::FailureCategory::model},
|
||||||
|
{"guide-at-threshold", replaceOnce(base, "0., 1., 0.", "1., 1e-12, 0."),
|
||||||
|
"invalid-beam-guide-vector", fesa::FailureCategory::model},
|
||||||
|
{"duplicate-elastic", replaceOnce(base, "100., 0.25\n*Boundary", "100., 0.25\n*Elastic\n100., 0.25\n*Boundary"),
|
||||||
|
"duplicate-entity", fesa::FailureCategory::input},
|
||||||
|
{"duplicate-node-label", replaceOnce(base, "2, 1., 0., 0.", "1, 1., 0., 0."),
|
||||||
|
"duplicate-entity", fesa::FailureCategory::input},
|
||||||
|
{"dangling-connectivity", replaceOnce(base, "1, 1, 2", "1, 1, 9"),
|
||||||
|
"unresolved-reference", fesa::FailureCategory::input},
|
||||||
|
{"invalid-dof", replaceOnce(base, "Root, 1, 6", "Root, 1, 7"),
|
||||||
|
"invalid-dof", fesa::FailureCategory::input},
|
||||||
|
{"nonfinite-coordinate", replaceOnce(base, "1., 0., 0.", "nan, 0., 0."),
|
||||||
|
"invalid-numeric-value", fesa::FailureCategory::input},
|
||||||
|
{"malformed-node-arity", replaceOnce(base, "1, 0., 0., 0.", "1, 0., 0."),
|
||||||
|
"invalid-data-arity", fesa::FailureCategory::input},
|
||||||
|
{"nlgeom", replaceOnce(base, "nlgeom=NO", "nlgeom=YES"),
|
||||||
|
"unsupported-nonlinear-geometry", fesa::FailureCategory::model},
|
||||||
|
{"unknown-keyword", replaceOnce(base, "*Assembly, name=Assembly", "*Density\n1.\n*Assembly, name=Assembly"),
|
||||||
|
"unsupported-keyword", fesa::FailureCategory::input},
|
||||||
|
{"invalid-static-arity", replaceOnce(base, "0.1, 1., 0.01, 1.", "0.1, 1., 0.01"),
|
||||||
|
"invalid-static-data", fesa::FailureCategory::input},
|
||||||
|
{"invalid-static-range", replaceOnce(base, "0.1, 1., 0.01, 1.", "0.1, 1., 2., 1."),
|
||||||
|
"invalid-static-data", fesa::FailureCategory::input},
|
||||||
|
{"invalid-generate", replaceOnce(base, "*Elset, elset=BeamSet\n1", "*Elset, elset=BeamSet, generate\n1, 1, 0"),
|
||||||
|
"invalid-set-range", fesa::FailureCategory::input},
|
||||||
|
{"nonlanding-generate", replaceOnce(base, "*Elset, elset=BeamSet\n1", "*Elset, elset=BeamSet, generate\n1, 2, 2"),
|
||||||
|
"invalid-set-range", fesa::FailureCategory::input},
|
||||||
|
{"ambiguous-direct-label", replaceOnce(
|
||||||
|
replaceOnce(base,
|
||||||
|
"*End Instance\n*Nset, nset=Root",
|
||||||
|
"*End Instance\n*Instance, name=Beam-2, part=BeamPart\n*End Instance\n*Nset, nset=Root"),
|
||||||
|
"Root, 1, 6",
|
||||||
|
"1, 1, 6"),
|
||||||
|
"unresolved-reference", fesa::FailureCategory::input},
|
||||||
|
{"ambiguous-part-set", replaceOnce(
|
||||||
|
replaceOnce(
|
||||||
|
replaceOnce(base,
|
||||||
|
"*Elset, elset=BeamSet",
|
||||||
|
"*Nset, nset=Local\n1\n*Elset, elset=BeamSet"),
|
||||||
|
"*End Instance\n*Nset, nset=Root",
|
||||||
|
"*End Instance\n*Instance, name=Beam-2, part=BeamPart\n*End Instance\n*Nset, nset=Root"),
|
||||||
|
"Root, 1, 6",
|
||||||
|
"Local, 1, 6"),
|
||||||
|
"unresolved-reference", fesa::FailureCategory::input},
|
||||||
|
{"direct-set-conflict", replaceOnce(base,
|
||||||
|
"*Step, name=Load",
|
||||||
|
"*Boundary\n1, 1, 1, 2.\n*Step, name=Load"),
|
||||||
|
"conflicting-boundary-condition", fesa::FailureCategory::input},
|
||||||
|
{"dangling-boundary-target", replaceOnce(base, "Root, 1, 6", "Missing, 1, 6"),
|
||||||
|
"unresolved-reference", fesa::FailureCategory::input},
|
||||||
|
{"conflicting-boundary", replaceOnce(base, "*Step, name=Load", "*Boundary\nRoot, 1, 1, 2.\n*Step, name=Load"),
|
||||||
|
"conflicting-boundary-condition", fesa::FailureCategory::input}};
|
||||||
|
|
||||||
|
for (const auto& testCase : cases) {
|
||||||
|
SCOPED_TRACE(testCase.name);
|
||||||
|
auto result = mapText("invalid-" + testCase.name, testCase.deck);
|
||||||
|
ASSERT_FALSE(result.hasValue());
|
||||||
|
EXPECT_EQ(result.status().failureCategory(), testCase.category);
|
||||||
|
const auto* diagnostic = findDiagnostic(result.status(), testCase.expectedCode);
|
||||||
|
ASSERT_NE(diagnostic, nullptr);
|
||||||
|
EXPECT_EQ(diagnostic->severity, fesa::Severity::error);
|
||||||
|
EXPECT_FALSE(diagnostic->location.file.empty());
|
||||||
|
EXPECT_GT(diagnostic->location.line, 0U);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(InpDomainMapping, RejectsDloadWithoutDistributedLoadObject) {
|
||||||
|
const auto deck = replaceOnce(
|
||||||
|
minimalDeck(),
|
||||||
|
"*Cload\nTip, 2, -1.\n",
|
||||||
|
"*Dload\nBeamSet, PY, -1.\n");
|
||||||
|
auto result = mapText("dload", deck);
|
||||||
|
|
||||||
|
ASSERT_FALSE(result.hasValue());
|
||||||
|
EXPECT_EQ(result.status().failureCategory(), fesa::FailureCategory::input);
|
||||||
|
const auto* diagnostic = findDiagnostic(result.status(), "unsupported-keyword");
|
||||||
|
ASSERT_NE(diagnostic, nullptr);
|
||||||
|
EXPECT_EQ(diagnostic->keyword, "DLOAD");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user