feat(linear-static-3d-euler-beam): step 10 - domain-model
This commit is contained in:
@@ -144,3 +144,48 @@
|
|||||||
`git diff --exit-code -- reference/` exited 0.
|
`git diff --exit-code -- reference/` exited 0.
|
||||||
- scope: the deferred empty-dot review minor was not changed, reference artifacts
|
- scope: the deferred empty-dot review minor was not changed, reference artifacts
|
||||||
remain untouched, and phase Step 9 status/timestamps were not modified.
|
remain untouched, and phase Step 9 status/timestamps were not modified.
|
||||||
|
|
||||||
|
## Step 10 — domain-model
|
||||||
|
|
||||||
|
- task_id: `TASK-10`
|
||||||
|
- status: `completed`
|
||||||
|
- changed_files: `include/fesa/model/model_types.hpp`,
|
||||||
|
`include/fesa/model/domain.hpp`, `src/fesa/model/domain.cpp`,
|
||||||
|
`tests/unit/model/model_types_test.cpp`, `tests/unit/model/domain_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-10-report.md`
|
||||||
|
- requirement_ids: `FESA-REQ-LS3DEB-015`, `FESA-REQ-LS3DEB-016`,
|
||||||
|
`FESA-REQ-LS3DEB-021`, `FESA-REQ-LS3DEB-030`,
|
||||||
|
`FESA-REQ-LS3DEB-034`
|
||||||
|
- test_ids: `T10-DOM-001`, `T10-DOM-002`, `T10-DOM-003`
|
||||||
|
|
||||||
|
| stage | exact command | exit_code | expected_or_observed_result | evidence_tail |
|
||||||
|
| --- | --- | ---: | --- | --- |
|
||||||
|
| RED | `cmake --build .harness/build --config Debug --target fesa_tests` | 1 | Both planned test translation units are registered before production and fail for the missing model API | MSVC C1083 for `fesa/model/domain.hpp` and `fesa/model/model_types.hpp`; the subsequent `ctest --test-dir .harness/build -C Debug -R DomainModel --output-on-failure` exited 0 with `No tests were found` because the new test executable could not build |
|
||||||
|
| GREEN-build | `cmake --build .harness/build --config Debug --target fesa_tests` | 0 | Minimal model records, immutable Domain ownership, and both tests compile and link | `domain.cpp`, `domain_test.cpp`, and `model_types_test.cpp` built; `fesa_solver.lib` and `fesa_unit_tests.exe` linked without a FESA warning under `/W4 /WX` |
|
||||||
|
| GREEN-test | `ctest --test-dir .harness/build -C Debug -R DomainModel --output-on-failure` | 0 | Source/internal identity, stable immutable ownership, and multiple-instance distinction pass | 3/3 exact `DomainModel` 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 | Configure and generate completed; oneMKL 2026.1 and the existing dependency targets resolved |
|
||||||
|
| 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 `/W4 /WX` |
|
||||||
|
| VERIFY-targeted | `ctest --test-dir .harness/build -C Debug -R DomainModel --output-on-failure` | 0 | Focused Step 10 suite remains green | 3/3 `DomainModel` tests passed |
|
||||||
|
| VERIFY-discovery | `ctest --test-dir .harness/build -C Debug --show-only=json-v1` | 0 | CTest discovers the accumulated suite and all exact DomainModel names | 10 tests discovered with `linear-static-3d-euler-beam` and `unit` labels |
|
||||||
|
| VERIFY-full | `ctest --test-dir .harness/build -C Debug --output-on-failure` | 0 | Full accumulated C++ suite has zero failures | 10/10 tests passed |
|
||||||
|
| VERIFY-dependency-direction | Backend scan over `include/fesa`; upward-include scan from `include/fesa/model`; core-to-model include scan from `include/fesa/core` using `rg -n` and fail-on-match wrappers | 0 | Public headers expose no MKL/PARDISO/TBB/HDF5 API and preserve `core -> model` dependency direction | backend leaks 0; model upward dependencies 0; core-to-model dependencies 0 |
|
||||||
|
| VERIFY-diff | `git diff --check` | 0 | Patch has no whitespace errors | Exit 0; only Git LF-to-CRLF working-copy notices were emitted |
|
||||||
|
| VERIFY-reference | `git diff --exit-code -- reference/` | 0 | Approved reference artifacts remain unchanged | Exit 0 and `git status --short -- reference/` produced no entries |
|
||||||
|
|
||||||
|
- contract_checks: every ledger record preserves `SourceLocation`; the static
|
||||||
|
step retains all four timing values; `PartDefinition`, `InstanceDefinition`,
|
||||||
|
and `SourceIndexMapping` retain source-to-internal identity; `ModelDefinition`
|
||||||
|
retains source path/content identity, heading, warnings, and all semantic
|
||||||
|
vectors. `Domain` owns the complete definition and returns const references;
|
||||||
|
declaration-order vector positions remain stable internal indices. Node and
|
||||||
|
beam definitions contain no equation-numbering state, and same-part identity
|
||||||
|
instances retain distinct source identities and connectivity.
|
||||||
|
- generated_evidence: `.harness/build/src/fesa/Debug/fesa_solver.lib`,
|
||||||
|
`.harness/build/tests/Debug/fesa_unit_tests.exe`
|
||||||
|
- reference_diff: unchanged; `git diff --exit-code -- reference/` exit 0
|
||||||
|
- handoff: backend-free `EntityIndex`, semantic model records,
|
||||||
|
`ModelDefinition`, and immutable `Domain::create`/const accessors are
|
||||||
|
available to Step 11 syntax parsing and Step 12 semantic mapping.
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fesa/core/status.hpp"
|
||||||
|
#include "fesa/model/model_types.hpp"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
// Owns the complete semantic definition. Public access remains const so a
|
||||||
|
// vector position can serve as a stable internal index after construction.
|
||||||
|
class Domain {
|
||||||
|
public:
|
||||||
|
static Result<Domain> create(ModelDefinition definition);
|
||||||
|
|
||||||
|
const std::vector<Node>& nodes() const noexcept;
|
||||||
|
const std::vector<EulerBeam3DDefinition>& elements() const noexcept;
|
||||||
|
const std::vector<LinearElasticMaterial>& materials() const noexcept;
|
||||||
|
const std::vector<GeneralBeamSection>& sections() const noexcept;
|
||||||
|
const std::vector<NodeSet>& nodeSets() const noexcept;
|
||||||
|
const std::vector<ElementSet>& elementSets() const noexcept;
|
||||||
|
const std::vector<StaticStepDefinition>& steps() const noexcept;
|
||||||
|
const std::vector<Diagnostic>& warnings() const noexcept;
|
||||||
|
const std::filesystem::path& sourcePath() const noexcept;
|
||||||
|
const std::string& sourceContentIdentity() const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Domain(ModelDefinition definition);
|
||||||
|
|
||||||
|
ModelDefinition definition_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fesa/core/diagnostic.hpp"
|
||||||
|
#include "fesa/core/source_identity.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
// Stable internal identities are vector positions assigned in declaration order.
|
||||||
|
using EntityIndex = std::uint32_t;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
SourceEntityId sourceId;
|
||||||
|
std::array<double, 3> coordinates;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LinearElasticMaterial {
|
||||||
|
std::string name;
|
||||||
|
double youngsModulus;
|
||||||
|
double poissonRatio;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GeneralBeamSection {
|
||||||
|
std::string name;
|
||||||
|
double area;
|
||||||
|
double i11;
|
||||||
|
double i12;
|
||||||
|
double i22;
|
||||||
|
double torsionalConstant;
|
||||||
|
std::array<double, 3> firstAxis;
|
||||||
|
std::vector<std::array<double, 2>> sectionPoints;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EulerBeam3DDefinition {
|
||||||
|
SourceEntityId sourceId;
|
||||||
|
std::array<EntityIndex, 2> nodeIndices;
|
||||||
|
EntityIndex materialIndex;
|
||||||
|
EntityIndex sectionIndex;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoundaryCondition {
|
||||||
|
std::string target;
|
||||||
|
int firstDof;
|
||||||
|
int lastDof;
|
||||||
|
double value;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NodalLoad {
|
||||||
|
std::string target;
|
||||||
|
int dof;
|
||||||
|
double magnitude;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StaticStepDefinition {
|
||||||
|
std::string name;
|
||||||
|
std::vector<BoundaryCondition> boundaries;
|
||||||
|
std::vector<NodalLoad> loads;
|
||||||
|
double initialIncrement;
|
||||||
|
double timePeriod;
|
||||||
|
double minimumIncrement;
|
||||||
|
double maximumIncrement;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NodeSet {
|
||||||
|
std::string name;
|
||||||
|
std::optional<std::string> instanceName;
|
||||||
|
std::vector<EntityIndex> nodeIndices;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ElementSet {
|
||||||
|
std::string name;
|
||||||
|
std::optional<std::string> instanceName;
|
||||||
|
std::vector<EntityIndex> elementIndices;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PartDefinition {
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::int64_t> nodeSourceLabels;
|
||||||
|
std::vector<std::int64_t> elementSourceLabels;
|
||||||
|
std::vector<std::string> nodeSetNames;
|
||||||
|
std::vector<std::string> elementSetNames;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SourceIndexMapping {
|
||||||
|
std::int64_t sourceLabel;
|
||||||
|
EntityIndex internalIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstanceDefinition {
|
||||||
|
std::string name;
|
||||||
|
std::string partName;
|
||||||
|
std::vector<SourceIndexMapping> nodeMappings;
|
||||||
|
std::vector<SourceIndexMapping> elementMappings;
|
||||||
|
SourceLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This construction-boundary value owns every parsed semantic record before
|
||||||
|
// it is finalized into an immutable Domain.
|
||||||
|
struct ModelDefinition {
|
||||||
|
std::filesystem::path sourcePath;
|
||||||
|
std::string sourceContentIdentity;
|
||||||
|
std::string heading;
|
||||||
|
std::vector<Node> nodes;
|
||||||
|
std::vector<EulerBeam3DDefinition> elements;
|
||||||
|
std::vector<LinearElasticMaterial> materials;
|
||||||
|
std::vector<GeneralBeamSection> sections;
|
||||||
|
std::vector<NodeSet> nodeSets;
|
||||||
|
std::vector<ElementSet> elementSets;
|
||||||
|
std::vector<PartDefinition> parts;
|
||||||
|
std::vector<InstanceDefinition> instances;
|
||||||
|
std::vector<StaticStepDefinition> steps;
|
||||||
|
std::vector<Diagnostic> warnings;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
@@ -6,6 +6,7 @@ add_library(
|
|||||||
core/status.cpp
|
core/status.cpp
|
||||||
math/matrix.cpp
|
math/matrix.cpp
|
||||||
math/vector.cpp
|
math/vector.cpp
|
||||||
|
model/domain.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#include "fesa/model/domain.hpp"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
Result<Domain> Domain::create(ModelDefinition definition) {
|
||||||
|
return Result<Domain>::success(Domain{std::move(definition)});
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Node>& Domain::nodes() const noexcept {
|
||||||
|
return definition_.nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<EulerBeam3DDefinition>& Domain::elements() const noexcept {
|
||||||
|
return definition_.elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<LinearElasticMaterial>& Domain::materials() const noexcept {
|
||||||
|
return definition_.materials;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<GeneralBeamSection>& Domain::sections() const noexcept {
|
||||||
|
return definition_.sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<NodeSet>& Domain::nodeSets() const noexcept {
|
||||||
|
return definition_.nodeSets;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<ElementSet>& Domain::elementSets() const noexcept {
|
||||||
|
return definition_.elementSets;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<StaticStepDefinition>& Domain::steps() const noexcept {
|
||||||
|
return definition_.steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Diagnostic>& Domain::warnings() const noexcept {
|
||||||
|
return definition_.warnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path& Domain::sourcePath() const noexcept {
|
||||||
|
return definition_.sourcePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Domain::sourceContentIdentity() const noexcept {
|
||||||
|
return definition_.sourceContentIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
|
Domain::Domain(ModelDefinition definition)
|
||||||
|
: definition_{std::move(definition)} {}
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
@@ -8,6 +8,8 @@ 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/model/domain_test.cpp
|
||||||
|
unit/model/model_types_test.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
#include "fesa/model/domain.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
fesa::ModelDefinition makeOwnedDefinition() {
|
||||||
|
fesa::ModelDefinition definition{};
|
||||||
|
definition.sourcePath = "models/owned.inp";
|
||||||
|
definition.sourceContentIdentity = "fnv1a64:fedcba9876543210";
|
||||||
|
definition.heading = "Stable declaration order";
|
||||||
|
definition.nodes = {
|
||||||
|
{{"Instance-A", 20, "0020"}, {2.0, 0.0, 0.0}, {"models/owned.inp", 12U}},
|
||||||
|
{{"Instance-A", 10, "0010"}, {1.0, 0.0, 0.0}, {"models/owned.inp", 11U}}};
|
||||||
|
definition.elements = {{
|
||||||
|
{"Instance-A", 5, "0005"},
|
||||||
|
{fesa::EntityIndex{1}, fesa::EntityIndex{0}},
|
||||||
|
fesa::EntityIndex{0},
|
||||||
|
fesa::EntityIndex{0},
|
||||||
|
{"models/owned.inp", 20U}}};
|
||||||
|
definition.materials = {{
|
||||||
|
"Steel", 210.0e9, 0.3, {"models/owned.inp", 30U}}};
|
||||||
|
definition.sections = {{
|
||||||
|
"Section-1",
|
||||||
|
0.02,
|
||||||
|
1.0e-5,
|
||||||
|
0.0,
|
||||||
|
2.0e-5,
|
||||||
|
5.0e-6,
|
||||||
|
{0.0, 1.0, 0.0},
|
||||||
|
{{-0.1, 0.0}, {0.1, 0.0}},
|
||||||
|
{"models/owned.inp", 40U}}};
|
||||||
|
definition.nodeSets = {{
|
||||||
|
"Tip", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 45U}}};
|
||||||
|
definition.elementSets = {{
|
||||||
|
"Beam", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 46U}}};
|
||||||
|
definition.parts = {{
|
||||||
|
"BeamPart", {20, 10}, {5}, {"Tip"}, {"Beam"},
|
||||||
|
{"models/owned.inp", 2U}}};
|
||||||
|
definition.instances = {{
|
||||||
|
"Instance-A",
|
||||||
|
"BeamPart",
|
||||||
|
{{20, 0U}, {10, 1U}},
|
||||||
|
{{5, 0U}},
|
||||||
|
{"models/owned.inp", 50U}}};
|
||||||
|
definition.steps = {{
|
||||||
|
"Step-1",
|
||||||
|
{{"Root", 1, 6, 0.0, {"models/owned.inp", 60U}}},
|
||||||
|
{{"Tip", 3, -1000.0, {"models/owned.inp", 61U}}},
|
||||||
|
0.1,
|
||||||
|
1.0,
|
||||||
|
1.0e-5,
|
||||||
|
1.0,
|
||||||
|
{"models/owned.inp", 55U}}};
|
||||||
|
definition.warnings = {{
|
||||||
|
fesa::Severity::warning,
|
||||||
|
"ignored-output-request",
|
||||||
|
{"models/owned.inp", 70U},
|
||||||
|
"*OUTPUT",
|
||||||
|
"",
|
||||||
|
"Ignored output request."}};
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) {
|
||||||
|
auto definition = makeOwnedDefinition();
|
||||||
|
auto result = fesa::Domain::create(definition);
|
||||||
|
ASSERT_TRUE(result.hasValue());
|
||||||
|
|
||||||
|
definition.sourcePath = "mutated.inp";
|
||||||
|
definition.sourceContentIdentity = "mutated";
|
||||||
|
definition.nodes[0].sourceId.sourceLabelText = "mutated";
|
||||||
|
definition.nodes[0].coordinates[0] = -99.0;
|
||||||
|
definition.sections[0].sectionPoints[0][0] = -99.0;
|
||||||
|
definition.steps[0].loads[0].magnitude = 99.0;
|
||||||
|
definition.warnings[0].code = "mutated";
|
||||||
|
|
||||||
|
const fesa::Domain& domain = result.value();
|
||||||
|
const fesa::Node* const firstNodeAddress = domain.nodes().data();
|
||||||
|
static_assert(std::is_same_v<
|
||||||
|
decltype(std::declval<const fesa::Domain&>().nodes()),
|
||||||
|
const std::vector<fesa::Node>&>);
|
||||||
|
static_assert(std::is_same_v<
|
||||||
|
decltype(std::declval<const fesa::Domain&>().elements()),
|
||||||
|
const std::vector<fesa::EulerBeam3DDefinition>&>);
|
||||||
|
|
||||||
|
EXPECT_EQ(domain.sourcePath(), std::filesystem::path{"models/owned.inp"});
|
||||||
|
EXPECT_EQ(domain.sourceContentIdentity(), "fnv1a64:fedcba9876543210");
|
||||||
|
ASSERT_EQ(domain.nodes().size(), 2U);
|
||||||
|
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabel, 20);
|
||||||
|
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabelText, "0020");
|
||||||
|
EXPECT_DOUBLE_EQ(domain.nodes()[0].coordinates[0], 2.0);
|
||||||
|
EXPECT_EQ(domain.nodes()[1].sourceId.sourceLabel, 10);
|
||||||
|
EXPECT_EQ(domain.nodes().data(), firstNodeAddress);
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.elements().size(), 1U);
|
||||||
|
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 1U);
|
||||||
|
EXPECT_EQ(domain.elements()[0].nodeIndices[1], 0U);
|
||||||
|
ASSERT_EQ(domain.materials().size(), 1U);
|
||||||
|
ASSERT_EQ(domain.sections().size(), 1U);
|
||||||
|
EXPECT_DOUBLE_EQ(domain.sections()[0].sectionPoints[0][0], -0.1);
|
||||||
|
ASSERT_EQ(domain.nodeSets().size(), 1U);
|
||||||
|
EXPECT_EQ(domain.nodeSets()[0].nodeIndices[0], 0U);
|
||||||
|
ASSERT_EQ(domain.elementSets().size(), 1U);
|
||||||
|
EXPECT_EQ(domain.elementSets()[0].elementIndices[0], 0U);
|
||||||
|
|
||||||
|
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);
|
||||||
|
EXPECT_DOUBLE_EQ(domain.steps()[0].loads[0].magnitude, -1000.0);
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.warnings().size(), 1U);
|
||||||
|
EXPECT_EQ(domain.warnings()[0].code, "ignored-output-request");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DomainModel, MultipleIdentityInstancesDoNotMerge) {
|
||||||
|
fesa::ModelDefinition definition{};
|
||||||
|
definition.sourcePath = "models/two-instances.inp";
|
||||||
|
definition.sourceContentIdentity = "fnv1a64:0011223344556677";
|
||||||
|
definition.nodes = {
|
||||||
|
{{"Instance-A", 1, "1"}, {0.0, 0.0, 0.0}, {"models/two-instances.inp", 10U}},
|
||||||
|
{{"Instance-A", 2, "2"}, {1.0, 0.0, 0.0}, {"models/two-instances.inp", 11U}},
|
||||||
|
{{"Instance-B", 1, "1"}, {0.0, 0.0, 0.0}, {"models/two-instances.inp", 10U}},
|
||||||
|
{{"Instance-B", 2, "2"}, {1.0, 0.0, 0.0}, {"models/two-instances.inp", 11U}}};
|
||||||
|
definition.elements = {
|
||||||
|
{{"Instance-A", 1, "1"}, {0U, 1U}, 0U, 0U, {"models/two-instances.inp", 20U}},
|
||||||
|
{{"Instance-B", 1, "1"}, {2U, 3U}, 0U, 0U, {"models/two-instances.inp", 20U}}};
|
||||||
|
definition.materials = {{
|
||||||
|
"Steel", 210.0e9, 0.3, {"models/two-instances.inp", 30U}}};
|
||||||
|
definition.sections = {{
|
||||||
|
"Section-1",
|
||||||
|
0.02,
|
||||||
|
1.0e-5,
|
||||||
|
0.0,
|
||||||
|
2.0e-5,
|
||||||
|
5.0e-6,
|
||||||
|
{0.0, 1.0, 0.0},
|
||||||
|
{},
|
||||||
|
{"models/two-instances.inp", 40U}}};
|
||||||
|
definition.parts = {{
|
||||||
|
"BeamPart", {1, 2}, {1}, {}, {}, {"models/two-instances.inp", 2U}}};
|
||||||
|
definition.instances = {
|
||||||
|
{"Instance-A", "BeamPart", {{1, 0U}, {2, 1U}}, {{1, 0U}},
|
||||||
|
{"models/two-instances.inp", 50U}},
|
||||||
|
{"Instance-B", "BeamPart", {{1, 2U}, {2, 3U}}, {{1, 1U}},
|
||||||
|
{"models/two-instances.inp", 51U}}};
|
||||||
|
definition.steps = {{
|
||||||
|
"Step-1", {}, {}, 0.1, 1.0, 1.0e-5, 1.0,
|
||||||
|
{"models/two-instances.inp", 60U}}};
|
||||||
|
|
||||||
|
auto result = fesa::Domain::create(std::move(definition));
|
||||||
|
ASSERT_TRUE(result.hasValue());
|
||||||
|
const fesa::Domain& domain = result.value();
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.nodes().size(), 4U);
|
||||||
|
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabel, 1);
|
||||||
|
EXPECT_EQ(domain.nodes()[2].sourceId.sourceLabel, 1);
|
||||||
|
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabelText, "1");
|
||||||
|
EXPECT_EQ(domain.nodes()[2].sourceId.sourceLabelText, "1");
|
||||||
|
EXPECT_NE(
|
||||||
|
domain.nodes()[0].sourceId.instanceName,
|
||||||
|
domain.nodes()[2].sourceId.instanceName);
|
||||||
|
|
||||||
|
ASSERT_EQ(domain.elements().size(), 2U);
|
||||||
|
EXPECT_EQ(domain.elements()[0].sourceId.sourceLabel, 1);
|
||||||
|
EXPECT_EQ(domain.elements()[1].sourceId.sourceLabel, 1);
|
||||||
|
EXPECT_EQ(domain.elements()[0].sourceId.instanceName, "Instance-A");
|
||||||
|
EXPECT_EQ(domain.elements()[1].sourceId.instanceName, "Instance-B");
|
||||||
|
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U);
|
||||||
|
EXPECT_EQ(domain.elements()[1].nodeIndices[0], 2U);
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#include "fesa/model/model_types.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template<class T, class = void>
|
||||||
|
struct HasEquationId : std::false_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct HasEquationId<T, std::void_t<decltype(std::declval<T&>().equationId)>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template<class T, class = void>
|
||||||
|
struct HasEquationIds : std::false_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct HasEquationIds<T, std::void_t<decltype(std::declval<T&>().equationIds)>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
|
||||||
|
const fesa::SourceLocation nodeLocation{
|
||||||
|
std::filesystem::path{"models/beam.inp"}, 11U};
|
||||||
|
const fesa::SourceEntityId firstIdentity{"Instance-A", 7, "0007"};
|
||||||
|
const fesa::SourceEntityId equalIdentity{"Instance-A", 7, "0007"};
|
||||||
|
const fesa::SourceEntityId laterIdentity{"Instance-B", 7, "0007"};
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
std::make_tuple(
|
||||||
|
firstIdentity.instanceName,
|
||||||
|
firstIdentity.sourceLabel,
|
||||||
|
firstIdentity.sourceLabelText),
|
||||||
|
std::make_tuple(
|
||||||
|
equalIdentity.instanceName,
|
||||||
|
equalIdentity.sourceLabel,
|
||||||
|
equalIdentity.sourceLabelText));
|
||||||
|
EXPECT_LT(
|
||||||
|
std::make_tuple(
|
||||||
|
firstIdentity.instanceName,
|
||||||
|
firstIdentity.sourceLabel,
|
||||||
|
firstIdentity.sourceLabelText),
|
||||||
|
std::make_tuple(
|
||||||
|
laterIdentity.instanceName,
|
||||||
|
laterIdentity.sourceLabel,
|
||||||
|
laterIdentity.sourceLabelText));
|
||||||
|
|
||||||
|
const fesa::Node node{firstIdentity, {1.0, 2.0, 3.0}, nodeLocation};
|
||||||
|
const fesa::LinearElasticMaterial material{
|
||||||
|
"Steel", 210.0e9, 0.3, {"models/beam.inp", 30U}};
|
||||||
|
const fesa::GeneralBeamSection section{
|
||||||
|
"Section-1",
|
||||||
|
0.02,
|
||||||
|
1.0e-5,
|
||||||
|
0.0,
|
||||||
|
2.0e-5,
|
||||||
|
5.0e-6,
|
||||||
|
{0.0, 1.0, 0.0},
|
||||||
|
{{-0.1, 0.0}, {0.1, 0.0}},
|
||||||
|
{"models/beam.inp", 40U}};
|
||||||
|
const fesa::EulerBeam3DDefinition element{
|
||||||
|
{"Instance-A", 3, "0003"},
|
||||||
|
{fesa::EntityIndex{5}, fesa::EntityIndex{9}},
|
||||||
|
fesa::EntityIndex{2},
|
||||||
|
fesa::EntityIndex{4},
|
||||||
|
{"models/beam.inp", 20U}};
|
||||||
|
const fesa::BoundaryCondition boundary{
|
||||||
|
"Root", 1, 6, 0.0, {"models/beam.inp", 60U}};
|
||||||
|
const fesa::NodalLoad load{
|
||||||
|
"Tip", 3, -1000.0, {"models/beam.inp", 70U}};
|
||||||
|
const fesa::StaticStepDefinition step{
|
||||||
|
"Step-1",
|
||||||
|
{boundary},
|
||||||
|
{load},
|
||||||
|
0.1,
|
||||||
|
1.0,
|
||||||
|
1.0e-5,
|
||||||
|
1.0,
|
||||||
|
{"models/beam.inp", 50U}};
|
||||||
|
const fesa::NodeSet nodeSet{
|
||||||
|
"Root", std::string{"Instance-A"}, {5U}, {"models/beam.inp", 25U}};
|
||||||
|
const fesa::ElementSet elementSet{
|
||||||
|
"Beam", std::string{"Instance-A"}, {4U}, {"models/beam.inp", 26U}};
|
||||||
|
const fesa::PartDefinition part{
|
||||||
|
"BeamPart", {7, 8}, {3}, {"Root", "Tip"}, {"Beam"},
|
||||||
|
{"models/beam.inp", 2U}};
|
||||||
|
const fesa::InstanceDefinition instance{
|
||||||
|
"Instance-A",
|
||||||
|
"BeamPart",
|
||||||
|
{{7, 5U}, {8, 9U}},
|
||||||
|
{{3, 4U}},
|
||||||
|
{"models/beam.inp", 80U}};
|
||||||
|
|
||||||
|
fesa::ModelDefinition definition{};
|
||||||
|
definition.sourcePath = "models/beam.inp";
|
||||||
|
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
|
||||||
|
definition.heading = "Identity mapping fixture";
|
||||||
|
definition.nodes = {node};
|
||||||
|
definition.elements = {element};
|
||||||
|
definition.materials = {material};
|
||||||
|
definition.sections = {section};
|
||||||
|
definition.nodeSets = {nodeSet};
|
||||||
|
definition.elementSets = {elementSet};
|
||||||
|
definition.parts = {part};
|
||||||
|
definition.instances = {instance};
|
||||||
|
definition.steps = {step};
|
||||||
|
definition.warnings = {{
|
||||||
|
fesa::Severity::warning,
|
||||||
|
"ignored-output-request",
|
||||||
|
{"models/beam.inp", 75U},
|
||||||
|
"*OUTPUT",
|
||||||
|
"",
|
||||||
|
"Output request does not alter mandatory FESA results."}};
|
||||||
|
|
||||||
|
ASSERT_EQ(definition.nodes.size(), 1U);
|
||||||
|
EXPECT_EQ(definition.nodes[0].sourceId.sourceLabelText, "0007");
|
||||||
|
EXPECT_EQ(definition.nodes[0].location.line, 11U);
|
||||||
|
ASSERT_EQ(definition.instances.size(), 1U);
|
||||||
|
EXPECT_EQ(definition.instances[0].partName, "BeamPart");
|
||||||
|
EXPECT_EQ(definition.instances[0].nodeMappings[0].sourceLabel, 7);
|
||||||
|
EXPECT_EQ(definition.instances[0].nodeMappings[0].internalIndex, 5U);
|
||||||
|
EXPECT_EQ(definition.instances[0].elementMappings[0].internalIndex, 4U);
|
||||||
|
EXPECT_EQ(definition.steps[0].initialIncrement, 0.1);
|
||||||
|
EXPECT_EQ(definition.steps[0].timePeriod, 1.0);
|
||||||
|
EXPECT_EQ(definition.steps[0].minimumIncrement, 1.0e-5);
|
||||||
|
EXPECT_EQ(definition.steps[0].maximumIncrement, 1.0);
|
||||||
|
ASSERT_EQ(definition.warnings.size(), 1U);
|
||||||
|
EXPECT_EQ(definition.warnings[0].location.line, 75U);
|
||||||
|
|
||||||
|
static_assert(!HasEquationId<fesa::Node>::value);
|
||||||
|
static_assert(!HasEquationIds<fesa::Node>::value);
|
||||||
|
static_assert(!HasEquationId<fesa::EulerBeam3DDefinition>::value);
|
||||||
|
static_assert(!HasEquationIds<fesa::EulerBeam3DDefinition>::value);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user