feat(cpp-object-oriented-modular-refactoring): step 3 - foundation-google-style

This commit is contained in:
KOKO\Mimi
2026-08-16 04:26:14 +09:00
parent 2628ed3488
commit 042edadffb
93 changed files with 3144 additions and 3175 deletions
+27 -27
View File
@@ -58,11 +58,11 @@ fesa::ModelDefinition makeDefinition() {
TEST(AnalysisModel, ClassifiesActiveEntitiesInStableOrder) {
auto domainResult = fesa::Domain::create(makeDefinition());
ASSERT_TRUE(domainResult.hasValue());
ASSERT_TRUE(domainResult.HasValue());
auto modelResult = fesa::AnalysisModel::create(domainResult.value());
ASSERT_TRUE(modelResult.hasValue());
const auto& model = modelResult.value();
auto modelResult = fesa::AnalysisModel::create(domainResult.Value());
ASSERT_TRUE(modelResult.HasValue());
const auto& model = modelResult.Value();
EXPECT_EQ(
model.activeElements(),
@@ -83,8 +83,8 @@ TEST(AnalysisModel, ClassifiesActiveEntitiesInStableOrder) {
TEST(AnalysisModel, ReferencesWithoutCopyingOrMutatingDomain) {
auto domainResult = fesa::Domain::create(makeDefinition());
ASSERT_TRUE(domainResult.hasValue());
const fesa::Domain& domain = domainResult.value();
ASSERT_TRUE(domainResult.HasValue());
const fesa::Domain& domain = domainResult.Value();
const auto* const elementAddress = domain.elements().data();
const auto* const materialAddress = domain.materials().data();
const auto* const sectionAddress = domain.sections().data();
@@ -92,8 +92,8 @@ TEST(AnalysisModel, ReferencesWithoutCopyingOrMutatingDomain) {
const double firstLoadMagnitude = domain.steps()[0].loads[0].magnitude;
auto modelResult = fesa::AnalysisModel::create(domain);
ASSERT_TRUE(modelResult.hasValue());
const auto& model = modelResult.value();
ASSERT_TRUE(modelResult.HasValue());
const auto& model = modelResult.Value();
EXPECT_EQ(&model.domain(), &domain);
EXPECT_EQ(&model.step(), &domain.steps()[0]);
@@ -117,19 +117,19 @@ TEST(AnalysisModel, RejectsMissingOrMultipleStep) {
auto missingDefinition = makeDefinition();
missingDefinition.steps.clear();
auto missingDomain = fesa::Domain::create(std::move(missingDefinition));
ASSERT_TRUE(missingDomain.hasValue());
ASSERT_TRUE(missingDomain.HasValue());
auto missing = fesa::AnalysisModel::create(missingDomain.value());
ASSERT_FALSE(missing.hasValue());
auto missing = fesa::AnalysisModel::create(missingDomain.Value());
ASSERT_FALSE(missing.HasValue());
EXPECT_EQ(
missing.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_EQ(missing.status().diagnostics().size(), 1U);
missing.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_EQ(missing.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
missing.status().diagnostics()[0].code,
missing.GetStatus().Diagnostics()[0].code,
"invalid-model-cardinality");
EXPECT_EQ(missing.status().diagnostics()[0].keyword, "STEP");
EXPECT_EQ(missing.status().diagnostics()[0].entityIdentity, "0");
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].keyword, "STEP");
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].entity_identity, "0");
auto multipleDefinition = makeDefinition();
auto secondStep = multipleDefinition.steps.front();
@@ -137,18 +137,18 @@ TEST(AnalysisModel, RejectsMissingOrMultipleStep) {
secondStep.location.line = 60U;
multipleDefinition.steps.push_back(std::move(secondStep));
auto multipleDomain = fesa::Domain::create(std::move(multipleDefinition));
ASSERT_TRUE(multipleDomain.hasValue());
ASSERT_TRUE(multipleDomain.HasValue());
auto multiple = fesa::AnalysisModel::create(multipleDomain.value());
ASSERT_FALSE(multiple.hasValue());
auto multiple = fesa::AnalysisModel::create(multipleDomain.Value());
ASSERT_FALSE(multiple.HasValue());
EXPECT_EQ(
multiple.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_EQ(multiple.status().diagnostics().size(), 1U);
multiple.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_EQ(multiple.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
multiple.status().diagnostics()[0].code,
multiple.GetStatus().Diagnostics()[0].code,
"unsupported-multiple-step");
EXPECT_EQ(multiple.status().diagnostics()[0].keyword, "STEP");
EXPECT_EQ(multiple.status().diagnostics()[0].entityIdentity, "Step-2");
EXPECT_EQ(multiple.status().diagnostics()[0].location.line, 60U);
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].keyword, "STEP");
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].entity_identity, "Step-2");
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].location.line, 60U);
}
+13 -13
View File
@@ -65,16 +65,16 @@ fesa::DofManager makeDofs() {
{definition.sourcePath, 19U}}};
auto domain = fesa::Domain::create(std::move(definition));
EXPECT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
EXPECT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
EXPECT_TRUE(dofs.hasValue());
return std::move(dofs.value());
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
EXPECT_TRUE(dofs.HasValue());
return std::move(dofs.Value());
}
void expectAllZero(const fesa::Vector& vector) {
for (std::size_t index = 0U; index < vector.size(); ++index) {
for (std::size_t index = 0U; index < vector.Size(); ++index) {
EXPECT_DOUBLE_EQ(vector[index], 0.0);
}
}
@@ -96,12 +96,12 @@ TEST(AnalysisState, AllocatesOnlyV0FullVectors) {
&constState.reaction()};
for (const auto* vector : vectors) {
EXPECT_EQ(vector->size(), dofs.fullDofCount());
EXPECT_EQ(vector->Size(), dofs.fullDofCount());
expectAllZero(*vector);
}
for (std::size_t left = 0U; left < std::size(vectors); ++left) {
for (std::size_t right = left + 1U; right < std::size(vectors); ++right) {
EXPECT_NE(vectors[left]->data(), vectors[right]->data());
EXPECT_NE(vectors[left]->Data(), vectors[right]->Data());
}
}
@@ -142,8 +142,8 @@ TEST(AnalysisState, CopiesOrMovesWithoutAliasing) {
{0U, 1, 0U, 0.0, 0.0, 10.0, "fesa-default"});
auto copied = original;
EXPECT_NE(copied.displacement().data(), original.displacement().data());
EXPECT_NE(copied.reaction().data(), original.reaction().data());
EXPECT_NE(copied.displacement().Data(), original.displacement().Data());
EXPECT_NE(copied.reaction().Data(), original.reaction().Data());
EXPECT_NE(copied.endpointResults().data(), original.endpointResults().data());
EXPECT_NE(copied.gaussResults().data(), original.gaussResults().data());
EXPECT_NE(copied.stressResults().data(), original.stressResults().data());
@@ -156,7 +156,7 @@ TEST(AnalysisState, CopiesOrMovesWithoutAliasing) {
EXPECT_EQ(moved.identity().stepName, "Step-1");
EXPECT_DOUBLE_EQ(moved.displacement()[0], 30.0);
EXPECT_DOUBLE_EQ(moved.endpointResults()[0].endAction[0], 20.0);
EXPECT_NE(moved.displacement().data(), original.displacement().data());
EXPECT_NE(moved.displacement().Data(), original.displacement().Data());
EXPECT_NE(moved.endpointResults().data(), original.endpointResults().data());
auto copyAssigned = fesa::AnalysisState::create(dofs, {"Other", 3U});
@@ -169,5 +169,5 @@ TEST(AnalysisState, CopiesOrMovesWithoutAliasing) {
moveAssigned = std::move(copyAssigned);
EXPECT_EQ(moveAssigned.identity().stepName, "Step-1");
EXPECT_DOUBLE_EQ(moveAssigned.reaction()[11], -20.0);
EXPECT_NE(moveAssigned.reaction().data(), original.reaction().data());
EXPECT_NE(moveAssigned.reaction().Data(), original.reaction().Data());
}
+62 -62
View File
@@ -54,25 +54,25 @@ LoadFixture makeFixture(
{source, 20U}}};
auto domainResult = fesa::Domain::create(std::move(definition));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{"Load fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Load fixture AnalysisModel construction failed."};
}
auto model = std::make_unique<fesa::AnalysisModel>(
std::move(modelResult.value()));
std::move(modelResult.Value()));
auto dofResult = fesa::DofManager::create(*model);
if (!dofResult.hasValue()) {
if (!dofResult.HasValue()) {
throw std::runtime_error{"Load fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofResult.value()));
std::move(dofResult.Value()));
return {std::move(domain), std::move(model), std::move(dofs)};
}
@@ -117,25 +117,25 @@ LoadFixture makeShellFixture(
{source, 20U}}};
auto domainResult = fesa::Domain::create(std::move(definition));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{"Shell load fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Shell load fixture AnalysisModel construction failed."};
}
auto model = std::make_unique<fesa::AnalysisModel>(
std::move(modelResult.value()));
std::move(modelResult.Value()));
auto dofResult = fesa::DofManager::create(*model);
if (!dofResult.hasValue()) {
if (!dofResult.HasValue()) {
throw std::runtime_error{"Shell load fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofResult.value()));
std::move(dofResult.Value()));
return {std::move(domain), std::move(model), std::move(dofs)};
}
@@ -164,21 +164,21 @@ fesa::SparseMatrix makeDenseSparse(
pattern.rowOffsets.push_back(pattern.columnIndices.size());
}
auto result = fesa::SparseMatrix::fromCoo(
auto result = fesa::SparseMatrix::FromCoo(
rows, columns, std::move(contributions), pattern);
if (!result.hasValue()) {
if (!result.HasValue()) {
throw std::runtime_error{"Sparse fixture construction failed."};
}
return std::move(result.value());
return std::move(result.Value());
}
void expectFailureCode(
const fesa::Result<fesa::Vector>& result,
const std::string& code) {
ASSERT_FALSE(result.hasValue());
EXPECT_EQ(result.status().failureCategory(), fesa::FailureCategory::model);
ASSERT_EQ(result.status().diagnostics().size(), 1U);
EXPECT_EQ(result.status().diagnostics()[0U].code, code);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code, code);
}
} // namespace
@@ -198,16 +198,16 @@ TEST(LoadAssembly, AssemblesNodeSetAndSixComponentLoads) {
auto result = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_TRUE(result.hasValue());
ASSERT_EQ(result.value().size(), 12U);
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(result.Value().Size(), 12U);
EXPECT_EQ(
std::vector<double>(result.value().data(), result.value().data() + 12U),
std::vector<double>(result.Value().Data(), result.Value().Data() + 12U),
(std::vector<double>{
1.0, 2.0, 0.0, -4.0, 5.0, 0.0,
1.0, 0.0, 3.0, 0.0, 5.0, 6.0}));
EXPECT_EQ(fixture.dofs->constrainedDofs(),
(std::vector<std::size_t>{0U}));
EXPECT_DOUBLE_EQ(result.value()[0U], 1.0);
EXPECT_DOUBLE_EQ(result.Value()[0U], 1.0);
}
TEST(LoadAssembly, AccumulatesSignedLoadsInSourceOrder) {
@@ -231,10 +231,10 @@ TEST(LoadAssembly, AccumulatesSignedLoadsInSourceOrder) {
*firstOrder.model, *firstOrder.dofs);
auto second = fesa::LoadAssembler::assembleFullNodalLoad(
*secondOrder.model, *secondOrder.dofs);
ASSERT_TRUE(first.hasValue());
ASSERT_TRUE(second.hasValue());
EXPECT_DOUBLE_EQ(first.value()[0U], 1.0);
EXPECT_DOUBLE_EQ(second.value()[0U], 0.0);
ASSERT_TRUE(first.HasValue());
ASSERT_TRUE(second.HasValue());
EXPECT_DOUBLE_EQ(first.Value()[0U], 1.0);
EXPECT_DOUBLE_EQ(second.Value()[0U], 0.0);
}
// MITC4-LOAD-001
@@ -257,10 +257,10 @@ TEST(LoadAssembly, AggregatesAllSixGlobalShellLoadComponentsInSourceOrder) {
const auto result = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_TRUE(result.hasValue());
ASSERT_EQ(result.value().size(), 24U);
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(result.Value().Size(), 24U);
EXPECT_EQ(
std::vector<double>(result.value().data(), result.value().data() + 6U),
std::vector<double>(result.Value().Data(), result.Value().Data() + 6U),
(std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 0.0}));
}
@@ -279,10 +279,10 @@ TEST(LoadAssembly, AcceptsExactlyZeroAggregateShellMoment) {
const auto result = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_TRUE(result.hasValue());
EXPECT_DOUBLE_EQ(result.value()[3U], 0.0);
EXPECT_DOUBLE_EQ(result.value()[4U], 0.0);
EXPECT_DOUBLE_EQ(result.value()[5U], 0.0);
ASSERT_TRUE(result.HasValue());
EXPECT_DOUBLE_EQ(result.Value()[3U], 0.0);
EXPECT_DOUBLE_EQ(result.Value()[4U], 0.0);
EXPECT_DOUBLE_EQ(result.Value()[5U], 0.0);
}
// MITC4-LOAD-003
@@ -302,15 +302,15 @@ TEST(LoadAssembly, EnforcesAggregateShellMomentDirectorProjectionThreshold) {
const auto rejected = fesa::LoadAssembler::assembleFullNodalLoad(
*rejectedFixture.model, *rejectedFixture.dofs);
ASSERT_TRUE(accepted.hasValue());
ASSERT_FALSE(rejected.hasValue());
EXPECT_EQ(rejected.status().failureCategory(), fesa::FailureCategory::model);
ASSERT_EQ(rejected.status().diagnostics().size(), 1U);
ASSERT_TRUE(accepted.HasValue());
ASSERT_FALSE(rejected.HasValue());
EXPECT_EQ(rejected.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(rejected.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
rejected.status().diagnostics()[0U].code,
rejected.GetStatus().Diagnostics()[0U].code,
"unsupported-drilling-load");
EXPECT_EQ(rejected.status().diagnostics()[0U].keyword, "CLOAD");
EXPECT_EQ(rejected.status().diagnostics()[0U].entityIdentity, "10");
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0U].keyword, "CLOAD");
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0U].entity_identity, "10");
}
// MITC4-LOAD-004
@@ -323,11 +323,11 @@ TEST(LoadAssembly, RejectsDrillingMomentBeforeEffectiveRhsCanBeFormed) {
const auto rejected = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_FALSE(rejected.hasValue());
EXPECT_EQ(rejected.status().failureCategory(), fesa::FailureCategory::model);
ASSERT_EQ(rejected.status().diagnostics().size(), 1U);
ASSERT_FALSE(rejected.HasValue());
EXPECT_EQ(rejected.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(rejected.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
rejected.status().diagnostics()[0U].code,
rejected.GetStatus().Diagnostics()[0U].code,
"unsupported-drilling-load");
}
@@ -346,7 +346,7 @@ TEST(LoadAssembly, FormsNonzeroPrescribedEffectiveRhs) {
{"10", 6, 40.0, {source, 35U}}});
auto full = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_TRUE(full.hasValue());
ASSERT_TRUE(full.HasValue());
const auto kfc = makeDenseSparse(
4U,
2U,
@@ -356,11 +356,11 @@ TEST(LoadAssembly, FormsNonzeroPrescribedEffectiveRhs) {
0.5, -1.0});
auto rhs = fesa::LoadAssembler::effectiveFreeRhs(
full.value(), kfc, fixture.dofs->prescribedValues(), *fixture.dofs);
ASSERT_TRUE(rhs.hasValue());
ASSERT_EQ(rhs.value().size(), 4U);
full.Value(), kfc, fixture.dofs->prescribedValues(), *fixture.dofs);
ASSERT_TRUE(rhs.HasValue());
ASSERT_EQ(rhs.Value().Size(), 4U);
EXPECT_EQ(
std::vector<double>(rhs.value().data(), rhs.value().data() + 4U),
std::vector<double>(rhs.Value().Data(), rhs.Value().Data() + 4U),
(std::vector<double>{10.0, 18.0, 39.0, 38.0}));
}
@@ -469,22 +469,22 @@ TEST(LoadAssembly, ZeroLoadsRemainZero) {
{{"10", 3, 0.0, {source, 30U}}});
auto full = fesa::LoadAssembler::assembleFullNodalLoad(
*freeFixture.model, *freeFixture.dofs);
ASSERT_TRUE(full.hasValue());
ASSERT_TRUE(full.HasValue());
EXPECT_TRUE(std::all_of(
full.value().data(),
full.value().data() + full.value().size(),
full.Value().Data(),
full.Value().Data() + full.Value().Size(),
[](const double value) { return value == 0.0; }));
const auto noConstrainedColumns = makeDenseSparse(6U, 0U, {});
auto freeRhs = fesa::LoadAssembler::effectiveFreeRhs(
full.value(),
full.Value(),
noConstrainedColumns,
freeFixture.dofs->prescribedValues(),
*freeFixture.dofs);
ASSERT_TRUE(freeRhs.hasValue());
EXPECT_EQ(freeRhs.value().size(), 6U);
ASSERT_TRUE(freeRhs.HasValue());
EXPECT_EQ(freeRhs.Value().Size(), 6U);
EXPECT_TRUE(std::all_of(
freeRhs.value().data(),
freeRhs.value().data() + freeRhs.value().size(),
freeRhs.Value().Data(),
freeRhs.Value().Data() + freeRhs.Value().Size(),
[](const double value) { return value == 0.0; }));
auto constrainedFixture = makeFixture(
@@ -494,13 +494,13 @@ TEST(LoadAssembly, ZeroLoadsRemainZero) {
{});
auto constrainedFull = fesa::LoadAssembler::assembleFullNodalLoad(
*constrainedFixture.model, *constrainedFixture.dofs);
ASSERT_TRUE(constrainedFull.hasValue());
ASSERT_TRUE(constrainedFull.HasValue());
const auto noFreeRows = makeDenseSparse(0U, 6U, {});
auto constrainedRhs = fesa::LoadAssembler::effectiveFreeRhs(
constrainedFull.value(),
constrainedFull.Value(),
noFreeRows,
constrainedFixture.dofs->prescribedValues(),
*constrainedFixture.dofs);
ASSERT_TRUE(constrainedRhs.hasValue());
EXPECT_EQ(constrainedRhs.value().size(), 0U);
ASSERT_TRUE(constrainedRhs.HasValue());
EXPECT_EQ(constrainedRhs.Value().Size(), 0U);
}
+84 -84
View File
@@ -115,10 +115,10 @@ fesa::Result<fesa::Mitc4Stiffness> directShellStiffness(
directors,
domain.shellSections().at(definition.sectionIndex),
domain.materials().at(definition.materialIndex));
if (!shell.hasValue()) {
return fesa::Result<fesa::Mitc4Stiffness>::failure(shell.status());
if (!shell.HasValue()) {
return fesa::Result<fesa::Mitc4Stiffness>::Failure(shell.GetStatus());
}
return shell.value().stiffness();
return shell.Value().stiffness();
}
fesa::Result<fesa::SparseMatrix> assembleShell(
@@ -127,19 +127,19 @@ fesa::Result<fesa::SparseMatrix> assembleShell(
const bool twoElements = false) {
auto domain = fesa::Domain::create(
makeShellDefinition(sourceType, twoElements));
if (!domain.hasValue()) {
return fesa::Result<fesa::SparseMatrix>::failure(domain.status());
if (!domain.HasValue()) {
return fesa::Result<fesa::SparseMatrix>::Failure(domain.GetStatus());
}
auto model = fesa::AnalysisModel::create(domain.value());
if (!model.hasValue()) {
return fesa::Result<fesa::SparseMatrix>::failure(model.status());
auto model = fesa::AnalysisModel::create(domain.Value());
if (!model.HasValue()) {
return fesa::Result<fesa::SparseMatrix>::Failure(model.GetStatus());
}
auto dofs = fesa::DofManager::create(model.value());
if (!dofs.hasValue()) {
return fesa::Result<fesa::SparseMatrix>::failure(dofs.status());
auto dofs = fesa::DofManager::create(model.Value());
if (!dofs.HasValue()) {
return fesa::Result<fesa::SparseMatrix>::Failure(dofs.GetStatus());
}
return fesa::SparseAssembler::assembleStiffness(
model.value(), dofs.value(), parallelFor);
model.Value(), dofs.Value(), parallelFor);
}
template<class T>
@@ -154,14 +154,14 @@ double entry(
const fesa::SparseMatrix& matrix,
const std::size_t row,
const std::size_t column) {
const auto begin = matrix.columnIndices().begin() + matrix.rowOffsets()[row];
const auto end = matrix.columnIndices().begin() + matrix.rowOffsets()[row + 1U];
const auto begin = matrix.ColumnIndices().begin() + matrix.RowOffsets()[row];
const auto end = matrix.ColumnIndices().begin() + matrix.RowOffsets()[row + 1U];
const auto found = std::lower_bound(begin, end, column);
if (found == end || *found != column) {
return 0.0;
}
return matrix.values()[static_cast<std::size_t>(
std::distance(matrix.columnIndices().begin(), found))];
return matrix.Values()[static_cast<std::size_t>(
std::distance(matrix.ColumnIndices().begin(), found))];
}
class ReverseParallelFor final : public fesa::ParallelFor {
@@ -192,66 +192,66 @@ private:
void expectByteIdentical(
const fesa::SparseMatrix& actual,
const fesa::SparseMatrix& expected) {
EXPECT_TRUE(byteIdentical(actual.rowOffsets(), expected.rowOffsets()));
EXPECT_TRUE(byteIdentical(actual.columnIndices(), expected.columnIndices()));
EXPECT_TRUE(byteIdentical(actual.values(), expected.values()));
EXPECT_TRUE(byteIdentical(actual.RowOffsets(), expected.RowOffsets()));
EXPECT_TRUE(byteIdentical(actual.ColumnIndices(), expected.ColumnIndices()));
EXPECT_TRUE(byteIdentical(actual.Values(), expected.Values()));
}
TEST(SparseAssembly, SerialTbbAndRepeatedRunsAreByteIdentical) {
auto domainResult = fesa::Domain::create(makeDefinition());
ASSERT_TRUE(domainResult.hasValue());
auto modelResult = fesa::AnalysisModel::create(domainResult.value());
ASSERT_TRUE(modelResult.hasValue());
auto dofsResult = fesa::DofManager::create(modelResult.value());
ASSERT_TRUE(dofsResult.hasValue());
ASSERT_TRUE(domainResult.HasValue());
auto modelResult = fesa::AnalysisModel::create(domainResult.Value());
ASSERT_TRUE(modelResult.HasValue());
auto dofsResult = fesa::DofManager::create(modelResult.Value());
ASSERT_TRUE(dofsResult.HasValue());
fesa::SerialParallelFor serialExecutor;
fesa::TbbParallelFor tbbExecutor;
ReverseParallelFor reverseExecutor;
auto serial = fesa::SparseAssembler::assembleStiffness(
modelResult.value(), dofsResult.value(), serialExecutor);
modelResult.Value(), dofsResult.Value(), serialExecutor);
auto tbb = fesa::SparseAssembler::assembleStiffness(
modelResult.value(), dofsResult.value(), tbbExecutor);
modelResult.Value(), dofsResult.Value(), tbbExecutor);
auto reversed = fesa::SparseAssembler::assembleStiffness(
modelResult.value(), dofsResult.value(), reverseExecutor);
ASSERT_TRUE(serial.hasValue());
ASSERT_TRUE(tbb.hasValue());
ASSERT_TRUE(reversed.hasValue());
modelResult.Value(), dofsResult.Value(), reverseExecutor);
ASSERT_TRUE(serial.HasValue());
ASSERT_TRUE(tbb.HasValue());
ASSERT_TRUE(reversed.HasValue());
EXPECT_EQ(reverseExecutor.calls(), 1U);
EXPECT_EQ(reverseExecutor.observedCount(), 2U);
EXPECT_EQ(serial.value().rows(), 18U);
EXPECT_EQ(serial.value().columns(), 18U);
EXPECT_EQ(serial.value().rowOffsets(), dofsResult.value().sparsePattern().rowOffsets);
EXPECT_EQ(serial.Value().Rows(), 18U);
EXPECT_EQ(serial.Value().Columns(), 18U);
EXPECT_EQ(serial.Value().RowOffsets(), dofsResult.Value().sparsePattern().rowOffsets);
EXPECT_EQ(
serial.value().columnIndices(),
dofsResult.value().sparsePattern().columnIndices);
EXPECT_TRUE(serial.value().validate().isOk());
expectByteIdentical(tbb.value(), serial.value());
expectByteIdentical(reversed.value(), serial.value());
serial.Value().ColumnIndices(),
dofsResult.Value().sparsePattern().columnIndices);
EXPECT_TRUE(serial.Value().Validate().IsOk());
expectByteIdentical(tbb.Value(), serial.Value());
expectByteIdentical(reversed.Value(), serial.Value());
for (std::size_t repetition = 0U; repetition < 8U; ++repetition) {
auto repeated = fesa::SparseAssembler::assembleStiffness(
modelResult.value(), dofsResult.value(), tbbExecutor);
ASSERT_TRUE(repeated.hasValue());
expectByteIdentical(repeated.value(), serial.value());
modelResult.Value(), dofsResult.Value(), tbbExecutor);
ASSERT_TRUE(repeated.HasValue());
expectByteIdentical(repeated.Value(), serial.Value());
}
for (std::size_t row = 0U; row < serial.value().rows(); ++row) {
for (std::size_t row = 0U; row < serial.Value().Rows(); ++row) {
for (std::size_t column = 0U;
column < serial.value().columns();
column < serial.Value().Columns();
++column) {
EXPECT_DOUBLE_EQ(
entry(serial.value(), row, column),
entry(serial.value(), column, row));
entry(serial.Value(), row, column),
entry(serial.Value(), column, row));
}
}
EXPECT_NEAR(entry(serial.value(), 0U, 0U), 120.0, 1.0e-12);
EXPECT_NEAR(entry(serial.value(), 0U, 6U), -120.0, 1.0e-12);
EXPECT_NEAR(entry(serial.value(), 6U, 6U), 200.0, 1.0e-12);
EXPECT_NEAR(entry(serial.value(), 6U, 12U), -80.0, 1.0e-12);
EXPECT_NEAR(entry(serial.value(), 12U, 12U), 80.0, 1.0e-12);
EXPECT_NEAR(entry(serial.Value(), 0U, 0U), 120.0, 1.0e-12);
EXPECT_NEAR(entry(serial.Value(), 0U, 6U), -120.0, 1.0e-12);
EXPECT_NEAR(entry(serial.Value(), 6U, 6U), 200.0, 1.0e-12);
EXPECT_NEAR(entry(serial.Value(), 6U, 12U), -80.0, 1.0e-12);
EXPECT_NEAR(entry(serial.Value(), 12U, 12U), 80.0, 1.0e-12);
}
TEST(
@@ -259,38 +259,38 @@ TEST(
AssemblesFourNodeTwentyFourDofKernelAndPreservesDiagonalSlots) {
auto domain = fesa::Domain::create(
makeShellDefinition(fesa::ShellSourceElementType::s4));
ASSERT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
ASSERT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
ASSERT_TRUE(dofs.hasValue());
ASSERT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
ASSERT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
ASSERT_TRUE(dofs.HasValue());
fesa::SerialParallelFor serialExecutor;
auto assembled = fesa::SparseAssembler::assembleStiffness(
model.value(), dofs.value(), serialExecutor);
auto expected = directShellStiffness(domain.value(), 0U);
ASSERT_TRUE(assembled.hasValue());
ASSERT_TRUE(expected.hasValue());
model.Value(), dofs.Value(), serialExecutor);
auto expected = directShellStiffness(domain.Value(), 0U);
ASSERT_TRUE(assembled.HasValue());
ASSERT_TRUE(expected.HasValue());
EXPECT_EQ(assembled.value().rows(), 24U);
EXPECT_EQ(assembled.value().columns(), 24U);
EXPECT_EQ(assembled.value().values().size(), 24U * 24U);
EXPECT_EQ(assembled.Value().Rows(), 24U);
EXPECT_EQ(assembled.Value().Columns(), 24U);
EXPECT_EQ(assembled.Value().Values().size(), 24U * 24U);
EXPECT_EQ(
assembled.value().rowOffsets(),
dofs.value().sparsePattern().rowOffsets);
assembled.Value().RowOffsets(),
dofs.Value().sparsePattern().rowOffsets);
EXPECT_EQ(
assembled.value().columnIndices(),
dofs.value().sparsePattern().columnIndices);
assembled.Value().ColumnIndices(),
dofs.Value().sparsePattern().columnIndices);
for (std::size_t row = 0U; row < 24U; ++row) {
const auto begin = assembled.value().columnIndices().begin() +
assembled.value().rowOffsets()[row];
const auto end = assembled.value().columnIndices().begin() +
assembled.value().rowOffsets()[row + 1U];
const auto begin = assembled.Value().ColumnIndices().begin() +
assembled.Value().RowOffsets()[row];
const auto end = assembled.Value().ColumnIndices().begin() +
assembled.Value().RowOffsets()[row + 1U];
EXPECT_NE(std::lower_bound(begin, end, row), end);
for (std::size_t column = 0U; column < 24U; ++column) {
EXPECT_DOUBLE_EQ(
entry(assembled.value(), row, column),
expected.value().stabilizedGlobal24(row, column));
entry(assembled.Value(), row, column),
expected.Value().stabilizedGlobal24(row, column));
}
}
}
@@ -305,19 +305,19 @@ TEST(SparseAssembly, ShellSerialTbbReverseAndRepeatedRunsAreByteIdentical) {
fesa::ShellSourceElementType::s4, tbbExecutor, true);
auto reversed = assembleShell(
fesa::ShellSourceElementType::s4, reverseExecutor, true);
ASSERT_TRUE(serial.hasValue());
ASSERT_TRUE(tbb.hasValue());
ASSERT_TRUE(reversed.hasValue());
ASSERT_TRUE(serial.HasValue());
ASSERT_TRUE(tbb.HasValue());
ASSERT_TRUE(reversed.HasValue());
EXPECT_EQ(reverseExecutor.calls(), 1U);
EXPECT_EQ(reverseExecutor.observedCount(), 2U);
expectByteIdentical(tbb.value(), serial.value());
expectByteIdentical(reversed.value(), serial.value());
expectByteIdentical(tbb.Value(), serial.Value());
expectByteIdentical(reversed.Value(), serial.Value());
for (std::size_t repetition = 0U; repetition < 8U; ++repetition) {
auto repeated = assembleShell(
fesa::ShellSourceElementType::s4, tbbExecutor, true);
ASSERT_TRUE(repeated.hasValue());
expectByteIdentical(repeated.value(), serial.value());
ASSERT_TRUE(repeated.HasValue());
expectByteIdentical(repeated.Value(), serial.Value());
}
}
@@ -327,13 +327,13 @@ TEST(SparseAssembly, S4AndS4rSemanticFixturesAssembleIdenticalStiffness) {
fesa::ShellSourceElementType::s4, serialExecutor);
auto s4r = assembleShell(
fesa::ShellSourceElementType::s4r, serialExecutor);
ASSERT_TRUE(s4.hasValue());
ASSERT_TRUE(s4r.hasValue());
ASSERT_TRUE(s4.HasValue());
ASSERT_TRUE(s4r.HasValue());
EXPECT_TRUE(std::any_of(
s4.value().values().begin(),
s4.value().values().end(),
s4.Value().Values().begin(),
s4.Value().Values().end(),
[](const double value) { return value != 0.0; }));
expectByteIdentical(s4r.value(), s4.value());
expectByteIdentical(s4r.Value(), s4.Value());
}
} // namespace
+12 -12
View File
@@ -1,4 +1,4 @@
#include "fesa/build_info.hpp"
#include "fesa/build_info.h"
#include <gtest/gtest.h>
@@ -8,20 +8,20 @@
#include <type_traits>
TEST(BuildInfo, VersionIsStableAndNonEmpty) {
const std::string_view first = fesa::solverVersion();
const std::string_view second = fesa::solverVersion();
const std::string_view first = fesa::SolverVersion();
const std::string_view second = fesa::SolverVersion();
EXPECT_FALSE(first.empty());
EXPECT_EQ(first, second);
EXPECT_TRUE(std::regex_match(
std::string(first), std::regex{R"(^[0-9]+\.[0-9]+\.[0-9]+$)"}));
EXPECT_FALSE(first.empty());
EXPECT_EQ(first, second);
EXPECT_TRUE(std::regex_match(std::string(first),
std::regex{R"(^[0-9]+\.[0-9]+\.[0-9]+$)"}));
}
TEST(BuildInfo, PublicHeaderHasNoBackendDependency) {
static_assert(
std::is_same_v<decltype(fesa::solverVersion()), std::string_view>,
"The public BuildInfo API must use only a standard-library value type.");
static_assert(noexcept(fesa::solverVersion()));
static_assert(
std::is_same_v<decltype(fesa::SolverVersion()), std::string_view>,
"The public BuildInfo API must use only a standard-library value type.");
static_assert(noexcept(fesa::SolverVersion()));
SUCCEED();
SUCCEED();
}
@@ -34,12 +34,12 @@ fesa::DofManager makeDofs(
{source, 10U}}};
auto domain = fesa::Domain::create(std::move(definition));
EXPECT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
EXPECT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
EXPECT_TRUE(dofs.hasValue());
return std::move(dofs.value());
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
EXPECT_TRUE(dofs.HasValue());
return std::move(dofs.Value());
}
fesa::DofManager makeShellSizedDofs(
@@ -75,12 +75,12 @@ fesa::DofManager makeShellSizedDofs(
{source, 10U}}};
auto domain = fesa::Domain::create(std::move(definition));
EXPECT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
EXPECT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
EXPECT_TRUE(dofs.hasValue());
return std::move(dofs.value());
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
EXPECT_TRUE(dofs.HasValue());
return std::move(dofs.Value());
}
fesa::SparseMatrix makeMatrix(
@@ -105,10 +105,10 @@ fesa::SparseMatrix makeMatrix(
pattern.rowOffsets.push_back(pattern.columnIndices.size());
}
auto matrix = fesa::SparseMatrix::fromCoo(
auto matrix = fesa::SparseMatrix::FromCoo(
rows, columns, std::move(contributions), pattern);
EXPECT_TRUE(matrix.hasValue());
return std::move(matrix.value());
EXPECT_TRUE(matrix.HasValue());
return std::move(matrix.Value());
}
std::vector<double> sequentialDense(const std::size_t size) {
@@ -126,9 +126,9 @@ void expectShape(
const fesa::SparseMatrix& matrix,
const std::size_t rows,
const std::size_t columns) {
EXPECT_EQ(matrix.rows(), rows);
EXPECT_EQ(matrix.columns(), columns);
EXPECT_TRUE(matrix.validate().isOk());
EXPECT_EQ(matrix.Rows(), rows);
EXPECT_EQ(matrix.Columns(), columns);
EXPECT_TRUE(matrix.Validate().IsOk());
}
} // namespace
@@ -142,45 +142,45 @@ TEST(EssentialConstraints, ExtractsHandComputedBlocksInStableOrder) {
const auto full = makeMatrix(6U, 6U, fullValues);
auto result = fesa::EssentialConstraints::partition(full, dofs);
ASSERT_TRUE(result.hasValue());
const auto& blocks = result.value();
ASSERT_TRUE(result.HasValue());
const auto& blocks = result.Value();
EXPECT_EQ(blocks.kff.rowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U, 12U, 16U}));
EXPECT_EQ(blocks.kff.RowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U, 12U, 16U}));
EXPECT_EQ(
blocks.kff.columnIndices(),
blocks.kff.ColumnIndices(),
(std::vector<std::size_t>{
0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U,
0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U}));
EXPECT_EQ(
blocks.kff.values(),
blocks.kff.Values(),
(std::vector<double>{
1.0, 3.0, 4.0, 6.0,
21.0, 23.0, 24.0, 26.0,
31.0, 33.0, 34.0, 36.0,
51.0, 53.0, 54.0, 56.0}));
EXPECT_EQ(blocks.kfc.rowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U, 6U, 8U}));
EXPECT_EQ(blocks.kfc.RowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U, 6U, 8U}));
EXPECT_EQ(
blocks.kfc.columnIndices(),
blocks.kfc.ColumnIndices(),
(std::vector<std::size_t>{0U, 1U, 0U, 1U, 0U, 1U, 0U, 1U}));
EXPECT_EQ(
blocks.kfc.values(),
blocks.kfc.Values(),
(std::vector<double>{2.0, 5.0, 22.0, 0.0, 32.0, 35.0, 52.0, 55.0}));
EXPECT_EQ(blocks.kcf.rowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U}));
EXPECT_EQ(blocks.kcf.RowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U}));
EXPECT_EQ(
blocks.kcf.columnIndices(),
blocks.kcf.ColumnIndices(),
(std::vector<std::size_t>{0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U}));
EXPECT_EQ(
blocks.kcf.values(),
blocks.kcf.Values(),
(std::vector<double>{11.0, 13.0, 14.0, 16.0, 41.0, 43.0, 44.0, 46.0}));
EXPECT_EQ(blocks.kcc.rowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U}));
EXPECT_EQ(blocks.kcc.columnIndices(), (std::vector<std::size_t>{0U, 1U, 0U, 1U}));
EXPECT_EQ(blocks.kcc.values(), (std::vector<double>{12.0, 15.0, 42.0, 45.0}));
EXPECT_EQ(blocks.kcc.RowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U}));
EXPECT_EQ(blocks.kcc.ColumnIndices(), (std::vector<std::size_t>{0U, 1U, 0U, 1U}));
EXPECT_EQ(blocks.kcc.Values(), (std::vector<double>{12.0, 15.0, 42.0, 45.0}));
EXPECT_EQ(blocks.kfc.values()[3U], 0.0);
EXPECT_TRUE(blocks.kff.validate().isOk());
EXPECT_TRUE(blocks.kfc.validate().isOk());
EXPECT_TRUE(blocks.kcf.validate().isOk());
EXPECT_TRUE(blocks.kcc.validate().isOk());
EXPECT_EQ(blocks.kfc.Values()[3U], 0.0);
EXPECT_TRUE(blocks.kff.Validate().IsOk());
EXPECT_TRUE(blocks.kfc.Validate().IsOk());
EXPECT_TRUE(blocks.kcf.Validate().IsOk());
EXPECT_TRUE(blocks.kcc.Validate().IsOk());
}
TEST(EssentialConstraints, HandlesNoAllAndMixedConstraints) {
@@ -188,29 +188,29 @@ TEST(EssentialConstraints, HandlesNoAllAndMixedConstraints) {
const auto noConstraints = makeDofs({});
auto none = fesa::EssentialConstraints::partition(full, noConstraints);
ASSERT_TRUE(none.hasValue());
expectShape(none.value().kff, 6U, 6U);
expectShape(none.value().kfc, 6U, 0U);
expectShape(none.value().kcf, 0U, 6U);
expectShape(none.value().kcc, 0U, 0U);
EXPECT_EQ(none.value().kff.values(), full.values());
ASSERT_TRUE(none.HasValue());
expectShape(none.Value().kff, 6U, 6U);
expectShape(none.Value().kfc, 6U, 0U);
expectShape(none.Value().kcf, 0U, 6U);
expectShape(none.Value().kcc, 0U, 0U);
EXPECT_EQ(none.Value().kff.Values(), full.Values());
const auto allConstraints = makeDofs({{"1", 1, 6, 1.0, {{}, 12U}}});
auto all = fesa::EssentialConstraints::partition(full, allConstraints);
ASSERT_TRUE(all.hasValue());
expectShape(all.value().kff, 0U, 0U);
expectShape(all.value().kfc, 0U, 6U);
expectShape(all.value().kcf, 6U, 0U);
expectShape(all.value().kcc, 6U, 6U);
EXPECT_EQ(all.value().kcc.values(), full.values());
ASSERT_TRUE(all.HasValue());
expectShape(all.Value().kff, 0U, 0U);
expectShape(all.Value().kfc, 0U, 6U);
expectShape(all.Value().kcf, 6U, 0U);
expectShape(all.Value().kcc, 6U, 6U);
EXPECT_EQ(all.Value().kcc.Values(), full.Values());
const auto mixedConstraints = makeDofs({{"1", 3, 4, 0.0, {{}, 12U}}});
auto mixed = fesa::EssentialConstraints::partition(full, mixedConstraints);
ASSERT_TRUE(mixed.hasValue());
expectShape(mixed.value().kff, 4U, 4U);
expectShape(mixed.value().kfc, 4U, 2U);
expectShape(mixed.value().kcf, 2U, 4U);
expectShape(mixed.value().kcc, 2U, 2U);
ASSERT_TRUE(mixed.HasValue());
expectShape(mixed.Value().kff, 4U, 4U);
expectShape(mixed.Value().kfc, 4U, 2U);
expectShape(mixed.Value().kcf, 2U, 4U);
expectShape(mixed.Value().kcc, 2U, 2U);
}
// MITC4-DOF-003
@@ -219,24 +219,24 @@ TEST(EssentialConstraints, PreservesShellSizedNoMixedAndAllConstraintRoundTrips)
const auto noConstraints = makeShellSizedDofs({});
auto none = fesa::EssentialConstraints::partition(full, noConstraints);
ASSERT_TRUE(none.hasValue());
expectShape(none.value().kff, 24U, 24U);
expectShape(none.value().kfc, 24U, 0U);
expectShape(none.value().kcf, 0U, 24U);
expectShape(none.value().kcc, 0U, 0U);
ASSERT_TRUE(none.HasValue());
expectShape(none.Value().kff, 24U, 24U);
expectShape(none.Value().kfc, 24U, 0U);
expectShape(none.Value().kcf, 0U, 24U);
expectShape(none.Value().kcc, 0U, 0U);
const auto mixedConstraints = makeShellSizedDofs({
{"1", 1, 6, 0.0, {{}, 12U}},
{"4", 2, 2, 2.5, {{}, 13U}}});
auto mixed = fesa::EssentialConstraints::partition(full, mixedConstraints);
ASSERT_TRUE(mixed.hasValue());
expectShape(mixed.value().kff, 17U, 17U);
expectShape(mixed.value().kfc, 17U, 7U);
expectShape(mixed.value().kcf, 7U, 17U);
expectShape(mixed.value().kcc, 7U, 7U);
ASSERT_TRUE(mixed.HasValue());
expectShape(mixed.Value().kff, 17U, 17U);
expectShape(mixed.Value().kfc, 17U, 7U);
expectShape(mixed.Value().kcf, 7U, 17U);
expectShape(mixed.Value().kcc, 7U, 7U);
fesa::Vector mixedFull{24U};
for (std::size_t index = 0U; index < mixedFull.size(); ++index) {
for (std::size_t index = 0U; index < mixedFull.Size(); ++index) {
mixedFull[index] = static_cast<double>(index) + 0.5;
}
for (std::size_t index = 0U;
@@ -250,8 +250,8 @@ TEST(EssentialConstraints, PreservesShellSizedNoMixedAndAllConstraintRoundTrips)
const auto mixedReconstructed =
fesa::EssentialConstraints::reconstructFull(
mixedFree, mixedConstraints.prescribedValues(), mixedConstraints);
ASSERT_EQ(mixedReconstructed.size(), mixedFull.size());
for (std::size_t index = 0U; index < mixedFull.size(); ++index) {
ASSERT_EQ(mixedReconstructed.Size(), mixedFull.Size());
for (std::size_t index = 0U; index < mixedFull.Size(); ++index) {
EXPECT_DOUBLE_EQ(mixedReconstructed[index], mixedFull[index]);
}
@@ -261,18 +261,18 @@ TEST(EssentialConstraints, PreservesShellSizedNoMixedAndAllConstraintRoundTrips)
{"3", 1, 6, 3.0, {{}, 16U}},
{"4", 1, 6, 4.0, {{}, 17U}}});
auto all = fesa::EssentialConstraints::partition(full, allConstraints);
ASSERT_TRUE(all.hasValue());
expectShape(all.value().kff, 0U, 0U);
expectShape(all.value().kfc, 0U, 24U);
expectShape(all.value().kcf, 24U, 0U);
expectShape(all.value().kcc, 24U, 24U);
ASSERT_TRUE(all.HasValue());
expectShape(all.Value().kff, 0U, 0U);
expectShape(all.Value().kfc, 0U, 24U);
expectShape(all.Value().kcf, 24U, 0U);
expectShape(all.Value().kcc, 24U, 24U);
const auto allReconstructed =
fesa::EssentialConstraints::reconstructFull(
fesa::Vector{0U},
allConstraints.prescribedValues(),
allConstraints);
ASSERT_EQ(allReconstructed.size(), 24U);
ASSERT_EQ(allReconstructed.Size(), 24U);
for (std::size_t node = 0U; node < 4U; ++node) {
for (std::size_t component = 0U; component < 6U; ++component) {
EXPECT_DOUBLE_EQ(allReconstructed[node * 6U + component], node + 1.0);
@@ -295,12 +295,12 @@ TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
const auto free = fesa::EssentialConstraints::gatherFree(full, dofs);
const auto constrained =
fesa::EssentialConstraints::gatherConstrained(full, dofs);
EXPECT_EQ(free.size(), 4U);
EXPECT_EQ(free.Size(), 4U);
EXPECT_DOUBLE_EQ(free[0U], 10.0);
EXPECT_DOUBLE_EQ(free[1U], 20.0);
EXPECT_DOUBLE_EQ(free[2U], 30.0);
EXPECT_DOUBLE_EQ(free[3U], 40.0);
EXPECT_EQ(constrained.size(), 2U);
EXPECT_EQ(constrained.Size(), 2U);
EXPECT_DOUBLE_EQ(constrained[0U], 2.5);
EXPECT_DOUBLE_EQ(constrained[1U], -3.25);
EXPECT_EQ(constrained[0U], dofs.prescribedValues()[0U]);
@@ -308,8 +308,8 @@ TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
const auto reconstructed = fesa::EssentialConstraints::reconstructFull(
free, dofs.prescribedValues(), dofs);
ASSERT_EQ(reconstructed.size(), full.size());
for (std::size_t index = 0U; index < full.size(); ++index) {
ASSERT_EQ(reconstructed.Size(), full.Size());
for (std::size_t index = 0U; index < full.Size(); ++index) {
EXPECT_DOUBLE_EQ(reconstructed[index], full[index]);
}
}
@@ -319,21 +319,21 @@ TEST(EssentialConstraints, RejectsDimensionOrOrderMismatch) {
const auto wrongSquare = makeMatrix(5U, 5U, sequentialDense(5U));
auto wrongDimension =
fesa::EssentialConstraints::partition(wrongSquare, dofs);
ASSERT_FALSE(wrongDimension.hasValue());
ASSERT_FALSE(wrongDimension.HasValue());
EXPECT_EQ(
wrongDimension.status().failureCategory(),
fesa::FailureCategory::model);
ASSERT_EQ(wrongDimension.status().diagnostics().size(), 1U);
wrongDimension.GetStatus().Category(),
fesa::FailureCategory::kModel);
ASSERT_EQ(wrongDimension.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
wrongDimension.status().diagnostics()[0U].code,
wrongDimension.GetStatus().Diagnostics()[0U].code,
"invalid-constraint-dimensions");
const auto rectangular = makeMatrix(
6U, 5U, std::vector<double>(30U, 0.0));
auto wrongOrder = fesa::EssentialConstraints::partition(rectangular, dofs);
ASSERT_FALSE(wrongOrder.hasValue());
ASSERT_FALSE(wrongOrder.HasValue());
EXPECT_EQ(
wrongOrder.status().diagnostics()[0U].code,
wrongOrder.GetStatus().Diagnostics()[0U].code,
"invalid-constraint-dimensions");
EXPECT_THROW(
+37 -41
View File
@@ -1,4 +1,4 @@
#include "fesa/core/diagnostic.hpp"
#include "fesa/core/diagnostic.h"
#include <gtest/gtest.h>
@@ -9,51 +9,47 @@
namespace {
fesa::Diagnostic makeDiagnostic(
std::string file,
std::size_t line,
std::string keyword,
std::string entityIdentity,
std::string code,
std::string message) {
return fesa::Diagnostic{
fesa::Severity::error,
std::move(code),
{std::filesystem::path{std::move(file)}, line},
std::move(keyword),
std::move(entityIdentity),
std::move(message)};
fesa::Diagnostic MakeDiagnostic(std::string file, std::size_t line,
std::string keyword,
std::string entity_identity, std::string code,
std::string message) {
return fesa::Diagnostic{fesa::Severity::kError,
std::move(code),
{std::filesystem::path{std::move(file)}, line},
std::move(keyword),
std::move(entity_identity),
std::move(message)};
}
} // namespace
} // namespace
TEST(CoreDiagnostics, DiagnosticsSortDeterministically) {
std::vector<fesa::Diagnostic> diagnostics{
makeDiagnostic("b.inp", 1U, "*NODE", "I.1", "a", "file-b"),
makeDiagnostic("a.inp", 3U, "*NODE", "I.1", "z", "code-z"),
makeDiagnostic("a.inp", 3U, "*NODE", "I.1", "a", "first-equal"),
makeDiagnostic("a.inp", 3U, "*NODE", "I.1", "a", "second-equal"),
makeDiagnostic("a.inp", 3U, "*NODE", "I.2", "a", "entity-2"),
makeDiagnostic("a.inp", 3U, "*BOUNDARY", "I.1", "a", "keyword"),
makeDiagnostic("a.inp", 2U, "*NODE", "I.1", "a", "line")};
std::vector<fesa::Diagnostic> diagnostics{
MakeDiagnostic("b.inp", 1U, "*NODE", "I.1", "a", "file-b"),
MakeDiagnostic("a.inp", 3U, "*NODE", "I.1", "z", "code-z"),
MakeDiagnostic("a.inp", 3U, "*NODE", "I.1", "a", "first-equal"),
MakeDiagnostic("a.inp", 3U, "*NODE", "I.1", "a", "second-equal"),
MakeDiagnostic("a.inp", 3U, "*NODE", "I.2", "a", "entity-2"),
MakeDiagnostic("a.inp", 3U, "*BOUNDARY", "I.1", "a", "keyword"),
MakeDiagnostic("a.inp", 2U, "*NODE", "I.1", "a", "line")};
fesa::sortDiagnostics(diagnostics);
fesa::SortDiagnostics(diagnostics);
ASSERT_EQ(diagnostics.size(), 7U);
EXPECT_EQ(diagnostics[0].message, "line");
EXPECT_EQ(diagnostics[1].message, "keyword");
EXPECT_EQ(diagnostics[2].message, "first-equal");
EXPECT_EQ(diagnostics[3].message, "second-equal");
EXPECT_EQ(diagnostics[4].message, "code-z");
EXPECT_EQ(diagnostics[5].message, "entity-2");
EXPECT_EQ(diagnostics[6].message, "file-b");
ASSERT_EQ(diagnostics.size(), 7U);
EXPECT_EQ(diagnostics[0].message, "line");
EXPECT_EQ(diagnostics[1].message, "keyword");
EXPECT_EQ(diagnostics[2].message, "first-equal");
EXPECT_EQ(diagnostics[3].message, "second-equal");
EXPECT_EQ(diagnostics[4].message, "code-z");
EXPECT_EQ(diagnostics[5].message, "entity-2");
EXPECT_EQ(diagnostics[6].message, "file-b");
const fesa::Diagnostic& exact = diagnostics[2];
EXPECT_EQ(exact.severity, fesa::Severity::error);
EXPECT_EQ(exact.code, "a");
EXPECT_EQ(exact.location.file, std::filesystem::path{"a.inp"});
EXPECT_EQ(exact.location.line, 3U);
EXPECT_EQ(exact.keyword, "*NODE");
EXPECT_EQ(exact.entityIdentity, "I.1");
EXPECT_EQ(exact.message, "first-equal");
const fesa::Diagnostic& exact = diagnostics[2];
EXPECT_EQ(exact.severity, fesa::Severity::kError);
EXPECT_EQ(exact.code, "a");
EXPECT_EQ(exact.location.file, std::filesystem::path{"a.inp"});
EXPECT_EQ(exact.location.line, 3U);
EXPECT_EQ(exact.keyword, "*NODE");
EXPECT_EQ(exact.entity_identity, "I.1");
EXPECT_EQ(exact.message, "first-equal");
}
+10 -10
View File
@@ -1,4 +1,4 @@
#include "fesa/core/source_identity.hpp"
#include "fesa/core/source_identity.h"
#include <gtest/gtest.h>
@@ -7,14 +7,14 @@
#include <string>
TEST(CoreDiagnostics, SourceIdentityPreservesRawIdentity) {
const fesa::SourceLocation location{
std::filesystem::path{"models/My Beam.inp"}, 27U};
const fesa::SourceEntityId identity{
"Beam-Instance_A", std::int64_t{42}, "00042"};
const fesa::SourceLocation location{
std::filesystem::path{"models/My Beam.inp"}, 27U};
const fesa::SourceEntityId identity{"Beam-Instance_A", std::int64_t{42},
"00042"};
EXPECT_EQ(location.file, std::filesystem::path{"models/My Beam.inp"});
EXPECT_EQ(location.line, 27U);
EXPECT_EQ(identity.instanceName, "Beam-Instance_A");
EXPECT_EQ(identity.sourceLabel, 42);
EXPECT_EQ(identity.sourceLabelText, "00042");
EXPECT_EQ(location.file, std::filesystem::path{"models/My Beam.inp"});
EXPECT_EQ(location.line, 27U);
EXPECT_EQ(identity.instance_name, "Beam-Instance_A");
EXPECT_EQ(identity.source_label, 42);
EXPECT_EQ(identity.source_label_text, "00042");
}
+42 -44
View File
@@ -1,4 +1,4 @@
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include <gtest/gtest.h>
@@ -10,57 +10,55 @@
namespace {
fesa::Diagnostic modelDiagnostic() {
return fesa::Diagnostic{
fesa::Severity::error,
"invalid-beam-length",
{std::filesystem::path{"beam.inp"}, 12U},
"*ELEMENT",
"Beam-1.10",
"Beam length must be positive."};
fesa::Diagnostic ModelDiagnostic() {
return fesa::Diagnostic{fesa::Severity::kError,
"invalid-beam-length",
{std::filesystem::path{"beam.inp"}, 12U},
"*ELEMENT",
"Beam-1.10",
"Beam length must be positive."};
}
} // namespace
} // namespace
TEST(CoreDiagnostics, ResultEnforcesValueErrorExclusivity) {
const fesa::Status ok = fesa::Status::ok();
EXPECT_TRUE(ok.isOk());
EXPECT_FALSE(ok.failureCategory().has_value());
EXPECT_TRUE(ok.diagnostics().empty());
const fesa::Status ok = fesa::Status::Ok();
EXPECT_TRUE(ok.IsOk());
EXPECT_FALSE(ok.Category().has_value());
EXPECT_TRUE(ok.Diagnostics().empty());
const fesa::Status uncategorized =
fesa::Status::failure(std::vector<fesa::Diagnostic>{modelDiagnostic()});
EXPECT_FALSE(uncategorized.isOk());
EXPECT_FALSE(uncategorized.failureCategory().has_value());
ASSERT_EQ(uncategorized.diagnostics().size(), 1U);
EXPECT_EQ(uncategorized.diagnostics()[0].code, "invalid-beam-length");
const fesa::Status uncategorized =
fesa::Status::Failure(std::vector<fesa::Diagnostic>{ModelDiagnostic()});
EXPECT_FALSE(uncategorized.IsOk());
EXPECT_FALSE(uncategorized.Category().has_value());
ASSERT_EQ(uncategorized.Diagnostics().size(), 1U);
EXPECT_EQ(uncategorized.Diagnostics()[0].code, "invalid-beam-length");
const fesa::Status categorized = fesa::Status::failure(
fesa::FailureCategory::model,
std::vector<fesa::Diagnostic>{modelDiagnostic()});
EXPECT_FALSE(categorized.isOk());
ASSERT_TRUE(categorized.failureCategory().has_value());
EXPECT_EQ(*categorized.failureCategory(), fesa::FailureCategory::model);
const fesa::Status categorized =
fesa::Status::Failure(fesa::FailureCategory::kModel,
std::vector<fesa::Diagnostic>{ModelDiagnostic()});
EXPECT_FALSE(categorized.IsOk());
ASSERT_TRUE(categorized.Category().has_value());
EXPECT_EQ(*categorized.Category(), fesa::FailureCategory::kModel);
const auto success = fesa::Result<std::string>::success("solved");
EXPECT_TRUE(success.hasValue());
EXPECT_TRUE(success.status().isOk());
EXPECT_EQ(success.value(), "solved");
const auto success = fesa::Result<std::string>::Success("solved");
EXPECT_TRUE(success.HasValue());
EXPECT_TRUE(success.GetStatus().IsOk());
EXPECT_EQ(success.Value(), "solved");
auto copied = success;
EXPECT_EQ(copied.value(), "solved");
auto moved = std::move(copied);
EXPECT_EQ(moved.value(), "solved");
auto copied = success;
EXPECT_EQ(copied.Value(), "solved");
auto moved = std::move(copied);
EXPECT_EQ(moved.Value(), "solved");
auto failure = fesa::Result<std::string>::failure(categorized);
EXPECT_FALSE(failure.hasValue());
EXPECT_FALSE(failure.status().isOk());
EXPECT_EQ(failure.status().failureCategory(), fesa::FailureCategory::model);
EXPECT_THROW(failure.value(), std::logic_error);
auto failure = fesa::Result<std::string>::Failure(categorized);
EXPECT_FALSE(failure.HasValue());
EXPECT_FALSE(failure.GetStatus().IsOk());
EXPECT_EQ(failure.GetStatus().Category(), fesa::FailureCategory::kModel);
EXPECT_THROW(failure.Value(), std::logic_error);
const auto& constFailure = failure;
EXPECT_THROW(constFailure.value(), std::logic_error);
EXPECT_THROW(
(void)fesa::Result<std::string>::failure(fesa::Status::ok()),
std::invalid_argument);
const auto& const_failure = failure;
EXPECT_THROW(const_failure.Value(), std::logic_error);
EXPECT_THROW((void)fesa::Result<std::string>::Failure(fesa::Status::Ok()),
std::invalid_argument);
}
+44 -44
View File
@@ -50,10 +50,10 @@ EulerBeam3D requireBeam(const Node& firstNode,
const GeneralBeamSection& section,
const LinearElasticMaterial& material) {
auto result = EulerBeam3D::create(firstNode, secondNode, section, material);
if (!result.hasValue()) {
if (!result.HasValue()) {
throw std::runtime_error{"Expected a valid EulerBeam3D fixture."};
}
return std::move(result.value());
return std::move(result.Value());
}
EulerBeam3D alignedBeam(double length,
@@ -68,8 +68,8 @@ EulerBeam3D alignedBeam(double length,
double maximumAbsoluteEntry(const Matrix& matrix) {
double maximum = 0.0;
for (std::size_t row = 0; row < matrix.rows(); ++row) {
for (std::size_t column = 0; column < matrix.columns(); ++column) {
for (std::size_t row = 0; row < matrix.Rows(); ++row) {
for (std::size_t column = 0; column < matrix.Columns(); ++column) {
maximum = (std::max)(maximum, std::abs(matrix(row, column)));
}
}
@@ -77,8 +77,8 @@ double maximumAbsoluteEntry(const Matrix& matrix) {
}
bool matrixIsFinite(const Matrix& matrix) {
for (std::size_t row = 0; row < matrix.rows(); ++row) {
for (std::size_t column = 0; column < matrix.columns(); ++column) {
for (std::size_t row = 0; row < matrix.Rows(); ++row) {
for (std::size_t column = 0; column < matrix.Columns(); ++column) {
if (!std::isfinite(matrix(row, column))) {
return false;
}
@@ -88,13 +88,13 @@ bool matrixIsFinite(const Matrix& matrix) {
}
double normalizedMatrixError(const Matrix& actual, const Matrix& expected) {
if (actual.rows() != expected.rows() || actual.columns() != expected.columns()) {
if (actual.Rows() != expected.Rows() || actual.Columns() != expected.Columns()) {
throw std::invalid_argument{"Matrix comparison requires equal shapes."};
}
double maximumDifference = 0.0;
for (std::size_t row = 0; row < actual.rows(); ++row) {
for (std::size_t column = 0; column < actual.columns(); ++column) {
for (std::size_t row = 0; row < actual.Rows(); ++row) {
for (std::size_t column = 0; column < actual.Columns(); ++column) {
maximumDifference = (std::max)(
maximumDifference,
std::abs(actual(row, column) - expected(row, column)));
@@ -109,16 +109,16 @@ double normalizedMatrixError(const Matrix& actual, const Matrix& expected) {
double vectorNorm(const Vector& vector) {
double sum = 0.0;
for (std::size_t index = 0; index < vector.size(); ++index) {
for (std::size_t index = 0; index < vector.Size(); ++index) {
sum += vector[index] * vector[index];
}
return std::sqrt(sum);
}
double quadraticEnergy(const Matrix& matrix, const Vector& vector) {
const Vector product = matrix.multiply(vector);
const Vector product = matrix.Multiply(vector);
double value = 0.0;
for (std::size_t index = 0; index < vector.size(); ++index) {
for (std::size_t index = 0; index < vector.Size(); ++index) {
value += vector[index] * product[index];
}
return value;
@@ -328,14 +328,14 @@ Vector solveFixedFirstNode(const Matrix& stiffness,
}
Vector solveDenseSystem(Matrix matrix, Vector rightHandSide) {
if (matrix.rows() != matrix.columns() ||
matrix.rows() != rightHandSide.size()) {
if (matrix.Rows() != matrix.Columns() ||
matrix.Rows() != rightHandSide.Size()) {
throw std::invalid_argument{"Dense test solve requires a square system."};
}
for (std::size_t pivot = 0; pivot < matrix.rows(); ++pivot) {
for (std::size_t pivot = 0; pivot < matrix.Rows(); ++pivot) {
std::size_t pivotRow = pivot;
for (std::size_t row = pivot + 1U; row < matrix.rows(); ++row) {
for (std::size_t row = pivot + 1U; row < matrix.Rows(); ++row) {
if (std::abs(matrix(row, pivot)) >
std::abs(matrix(pivotRow, pivot))) {
pivotRow = row;
@@ -345,22 +345,22 @@ Vector solveDenseSystem(Matrix matrix, Vector rightHandSide) {
!std::isfinite(matrix(pivotRow, pivot))) {
throw std::runtime_error{"Uniform-load test fixture is singular."};
}
for (std::size_t column = pivot; column < matrix.columns(); ++column) {
for (std::size_t column = pivot; column < matrix.Columns(); ++column) {
std::swap(matrix(pivot, column), matrix(pivotRow, column));
}
std::swap(rightHandSide[pivot], rightHandSide[pivotRow]);
const double pivotValue = matrix(pivot, pivot);
for (std::size_t column = pivot; column < matrix.columns(); ++column) {
for (std::size_t column = pivot; column < matrix.Columns(); ++column) {
matrix(pivot, column) /= pivotValue;
}
rightHandSide[pivot] /= pivotValue;
for (std::size_t row = 0; row < matrix.rows(); ++row) {
for (std::size_t row = 0; row < matrix.Rows(); ++row) {
if (row == pivot) {
continue;
}
const double factor = matrix(row, pivot);
for (std::size_t column = pivot; column < matrix.columns(); ++column) {
for (std::size_t column = pivot; column < matrix.Columns(); ++column) {
matrix(row, column) -= factor * matrix(pivot, column);
}
rightHandSide[row] -= factor * rightHandSide[pivot];
@@ -489,12 +489,12 @@ Matrix transformationFromKnownRows(
}
Vector transposeMultiply(const Matrix& matrix, const Vector& vector) {
if (matrix.rows() != vector.size()) {
if (matrix.Rows() != vector.Size()) {
throw std::invalid_argument{"Transpose multiply dimension mismatch."};
}
Vector result{matrix.columns()};
for (std::size_t column = 0; column < matrix.columns(); ++column) {
for (std::size_t row = 0; row < matrix.rows(); ++row) {
Vector result{matrix.Columns()};
for (std::size_t column = 0; column < matrix.Columns(); ++column) {
for (std::size_t row = 0; row < matrix.Rows(); ++row) {
result[column] += matrix(row, column) * vector[row];
}
}
@@ -595,9 +595,9 @@ TEST(EulerBeam3D, TwoPointGaussMatchesClosedStiffness) {
const Matrix closed = expectedClosedStiffness(length, section, material);
EXPECT_LE(normalizedMatrixError(actual, closed), kMatrixTolerance);
Matrix transpose{actual.rows(), actual.columns()};
for (std::size_t row = 0; row < actual.rows(); ++row) {
for (std::size_t column = 0; column < actual.columns(); ++column) {
Matrix transpose{actual.Rows(), actual.Columns()};
for (std::size_t row = 0; row < actual.Rows(); ++row) {
for (std::size_t column = 0; column < actual.Columns(); ++column) {
transpose(row, column) = actual(column, row);
}
}
@@ -629,7 +629,7 @@ TEST(EulerBeam3D, HasSixRigidModesRankSixAndPositiveDeformationEnergy) {
const double stiffnessScale = (std::max)(1.0, maximumAbsoluteEntry(stiffness));
for (const Vector& mode : rigidModes) {
const double normalizedResidual =
vectorNorm(stiffness.multiply(mode)) /
vectorNorm(stiffness.Multiply(mode)) /
(stiffnessScale * (std::max)(1.0, vectorNorm(mode)));
EXPECT_LE(normalizedResidual, kRigidTolerance);
}
@@ -695,7 +695,7 @@ TEST(EulerBeam3D, RotatedTransformPreservesWorkAndEnergy) {
const Matrix global = beam.globalStiffness();
Matrix expectedGlobal{kElementDofCount, kElementDofCount};
const Matrix localTimesTransform = local.multiply(transformation);
const Matrix localTimesTransform = local.Multiply(transformation);
for (std::size_t row = 0; row < kElementDofCount; ++row) {
for (std::size_t column = 0; column < kElementDofCount; ++column) {
for (std::size_t inner = 0; inner < kElementDofCount; ++inner) {
@@ -707,12 +707,12 @@ TEST(EulerBeam3D, RotatedTransformPreservesWorkAndEnergy) {
EXPECT_LE(normalizedMatrixError(global, expectedGlobal), kMatrixTolerance);
Vector localDisplacement{kElementDofCount};
for (std::size_t index = 0; index < localDisplacement.size(); ++index) {
for (std::size_t index = 0; index < localDisplacement.Size(); ++index) {
localDisplacement[index] = 0.01 * static_cast<double>(index + 1U) - 0.04;
}
const Vector globalDisplacement = transposeMultiply(transformation, localDisplacement);
const Vector localForce = local.multiply(localDisplacement);
const Vector globalForce = global.multiply(globalDisplacement);
const Vector localForce = local.Multiply(localDisplacement);
const Vector globalForce = global.Multiply(globalDisplacement);
const Vector expectedGlobalForce = transposeMultiply(transformation, localForce);
for (std::size_t index = 0; index < kElementDofCount; ++index) {
expectScaledNear(globalForce[index], expectedGlobalForce[index], kMatrixTolerance);
@@ -723,13 +723,13 @@ TEST(EulerBeam3D, RotatedTransformPreservesWorkAndEnergy) {
kMatrixTolerance);
Vector globalVariation{kElementDofCount};
for (std::size_t index = 0; index < globalVariation.size(); ++index) {
for (std::size_t index = 0; index < globalVariation.Size(); ++index) {
globalVariation[index] = 0.03 - 0.002 * static_cast<double>(index);
}
const Vector localVariation = transformation.multiply(globalVariation);
const Vector localVariation = transformation.Multiply(globalVariation);
expectScaledNear(
globalVariation.dot(globalForce),
localVariation.dot(localForce),
globalVariation.Dot(globalForce),
localVariation.Dot(localForce),
kMatrixTolerance);
const BeamRecovery recovery = beam.recover(globalDisplacement);
@@ -746,7 +746,7 @@ TEST(EulerBeam3D, ConstantLineLoadMatchesAllSignedComponents) {
const std::array<double, kElementDofCount> expected = {
5.0, -6.0, 11.0, -14.0, -22.0 / 3.0, -4.0,
5.0, -6.0, 11.0, -14.0, 22.0 / 3.0, 4.0};
ASSERT_EQ(equivalent.size(), expected.size());
ASSERT_EQ(equivalent.Size(), expected.size());
for (std::size_t index = 0; index < expected.size(); ++index) {
expectScaledNear(equivalent[index], expected[index], kMatrixTolerance);
}
@@ -931,17 +931,17 @@ TEST(EulerBeam3D, RejectsInvalidGeometryAndProperties) {
const auto expectFailure = [](const Result<EulerBeam3D>& result,
const std::string& code) {
if (result.hasValue()) {
const Matrix stiffness = result.value().localStiffness();
if (result.HasValue()) {
const Matrix stiffness = result.Value().localStiffness();
ADD_FAILURE()
<< "Invalid fixture was accepted; local stiffness finite="
<< matrixIsFinite(stiffness)
<< ", maximum absolute entry=" << maximumAbsoluteEntry(stiffness);
return;
}
EXPECT_EQ(result.status().failureCategory(), FailureCategory::model);
ASSERT_EQ(result.status().diagnostics().size(), 1U);
EXPECT_EQ(result.status().diagnostics()[0U].code, code);
EXPECT_EQ(result.GetStatus().Category(), FailureCategory::kModel);
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code, code);
};
expectFailure(
@@ -964,7 +964,7 @@ TEST(EulerBeam3D, RejectsInvalidGeometryAndProperties) {
"invalid-beam-length");
EXPECT_TRUE(EulerBeam3D::create(
scaledFirst, aboveThreshold, validSection, validMaterial)
.hasValue());
.HasValue());
auto parallelGuide = validSection;
parallelGuide.firstAxis = {1.0, 0.0, 0.0};
@@ -978,7 +978,7 @@ TEST(EulerBeam3D, RejectsInvalidGeometryAndProperties) {
"invalid-beam-guide-vector");
auto guideAboveThreshold = validSection;
guideAboveThreshold.firstAxis = {1.0, 2.0e-12, 0.0};
EXPECT_TRUE(EulerBeam3D::create(origin, unitX, guideAboveThreshold, validMaterial).hasValue());
EXPECT_TRUE(EulerBeam3D::create(origin, unitX, guideAboveThreshold, validMaterial).HasValue());
auto invalidMaterial = validMaterial;
invalidMaterial.youngsModulus = 0.0;
+113 -113
View File
@@ -84,10 +84,10 @@ void expectMatrixNear(
const fesa::Matrix& actual,
const fesa::Matrix& expected,
double tolerance = 1.0e-12) {
ASSERT_EQ(actual.rows(), expected.rows());
ASSERT_EQ(actual.columns(), expected.columns());
for (std::size_t row = 0U; row < actual.rows(); ++row) {
for (std::size_t column = 0U; column < actual.columns(); ++column) {
ASSERT_EQ(actual.Rows(), expected.Rows());
ASSERT_EQ(actual.Columns(), expected.Columns());
for (std::size_t row = 0U; row < actual.Rows(); ++row) {
for (std::size_t column = 0U; column < actual.Columns(); ++column) {
EXPECT_NEAR(actual(row, column), expected(row, column), tolerance)
<< "at (" << row << ", " << column << ")";
}
@@ -95,20 +95,20 @@ void expectMatrixNear(
}
void expectSymmetric(const fesa::Matrix& matrix) {
ASSERT_EQ(matrix.rows(), matrix.columns());
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
for (std::size_t column = 0U; column < matrix.columns(); ++column) {
ASSERT_EQ(matrix.Rows(), matrix.Columns());
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
EXPECT_NEAR(matrix(row, column), matrix(column, row), 1.0e-12);
}
}
}
bool hasPositiveCholeskyPivots(const fesa::Matrix& matrix) {
if (matrix.rows() != matrix.columns()) {
if (matrix.Rows() != matrix.Columns()) {
return false;
}
fesa::Matrix lower{matrix.rows(), matrix.columns()};
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
fesa::Matrix lower{matrix.Rows(), matrix.Columns()};
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
for (std::size_t column = 0U; column <= row; ++column) {
double value = matrix(row, column);
for (std::size_t inner = 0U; inner < column; ++inner) {
@@ -129,8 +129,8 @@ bool hasPositiveCholeskyPivots(const fesa::Matrix& matrix) {
double frobeniusNorm(const fesa::Matrix& matrix) {
double squaredNorm = 0.0;
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
for (std::size_t column = 0U; column < matrix.columns(); ++column) {
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
squaredNorm += matrix(row, column) * matrix(row, column);
}
}
@@ -141,11 +141,11 @@ double scaledSymmetryError(
const fesa::Matrix& matrix,
std::size_t dofsPerNode,
double elementLength) {
fesa::Matrix difference{matrix.rows(), matrix.columns()};
fesa::Matrix scaled{matrix.rows(), matrix.columns()};
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
fesa::Matrix difference{matrix.Rows(), matrix.Columns()};
fesa::Matrix scaled{matrix.Rows(), matrix.Columns()};
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
const double rowScale = row % dofsPerNode < 3U ? elementLength : 1.0;
for (std::size_t column = 0U; column < matrix.columns(); ++column) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
const double columnScale =
column % dofsPerNode < 3U ? elementLength : 1.0;
scaled(row, column) =
@@ -161,10 +161,10 @@ fesa::Matrix scaledStiffness(
const fesa::Matrix& matrix,
std::size_t dofsPerNode,
double elementLength) {
fesa::Matrix scaled{matrix.rows(), matrix.columns()};
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
fesa::Matrix scaled{matrix.Rows(), matrix.Columns()};
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
const double rowScale = row % dofsPerNode < 3U ? elementLength : 1.0;
for (std::size_t column = 0U; column < matrix.columns(); ++column) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
const double columnScale =
column % dofsPerNode < 3U ? elementLength : 1.0;
scaled(row, column) =
@@ -175,10 +175,10 @@ fesa::Matrix scaledStiffness(
}
std::vector<double> symmetricEigenvalues(fesa::Matrix matrix) {
if (matrix.rows() != matrix.columns()) {
if (matrix.Rows() != matrix.Columns()) {
throw std::invalid_argument{"Symmetric eigensolve requires a square matrix."};
}
const std::size_t size = matrix.rows();
const std::size_t size = matrix.Rows();
double matrixScale = 0.0;
for (std::size_t row = 0U; row < size; ++row) {
for (std::size_t column = 0U; column < size; ++column) {
@@ -266,7 +266,7 @@ double symmetricOperatorNorm(const fesa::Matrix& matrix) {
}
double quadraticEnergy(const fesa::Matrix& stiffness, const fesa::Vector& vector) {
return 0.5 * vector.dot(stiffness.multiply(vector));
return 0.5 * vector.Dot(stiffness.Multiply(vector));
}
fesa::Vector physicalField(const std::array<std::array<double, 5>, 4>& values) {
@@ -286,7 +286,7 @@ void expectStrain(
double eta,
double zeta,
const std::array<double, 5>& expected) {
const auto actual = shell.strainDisplacement20(xi, eta, zeta).multiply(field);
const auto actual = shell.strainDisplacement20(xi, eta, zeta).Multiply(field);
for (std::size_t component = 0U; component < expected.size(); ++component) {
EXPECT_NEAR(actual[component], expected[component], 1.0e-12)
<< "component " << component;
@@ -367,8 +367,8 @@ TEST(Mitc4ShellKinematics, BuildsRightHandedFramesAndSeparatePhysicalDrillingMap
node(4, {0.0, -1.0, 1.0})};
const auto candidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors({1.0, 0.0, 0.0}), section(), material());
ASSERT_TRUE(candidate.hasValue());
const auto& shell = candidate.value();
ASSERT_TRUE(candidate.HasValue());
const auto& shell = candidate.Value();
const auto frame = shell.localFrame(0.0, 0.0);
expectVectorNear(frame.e1, {0.0, 1.0, 0.0});
@@ -378,10 +378,10 @@ TEST(Mitc4ShellKinematics, BuildsRightHandedFramesAndSeparatePhysicalDrillingMap
const auto physical = shell.physicalTransformation20();
const auto drilling = shell.drillingTransformation4();
ASSERT_EQ(physical.rows(), 20U);
ASSERT_EQ(physical.columns(), 24U);
ASSERT_EQ(drilling.rows(), 4U);
ASSERT_EQ(drilling.columns(), 24U);
ASSERT_EQ(physical.Rows(), 20U);
ASSERT_EQ(physical.Columns(), 24U);
ASSERT_EQ(drilling.Rows(), 4U);
ASSERT_EQ(drilling.Columns(), 24U);
for (std::size_t nodeIndex = 0U; nodeIndex < 4U; ++nodeIndex) {
const std::size_t physicalOffset = 5U * nodeIndex;
const std::size_t globalOffset = 6U * nodeIndex;
@@ -411,7 +411,7 @@ TEST(Mitc4ShellKinematics, BuildsRightHandedFramesAndSeparatePhysicalDrillingMap
invalidDirectors[2] = {0.0, 0.0, 0.0};
EXPECT_FALSE(fesa::Mitc4Shell::create(
nodePointers(nodes), invalidDirectors, section(), material())
.hasValue());
.HasValue());
}
// MITC4-KIN-003
@@ -419,12 +419,12 @@ TEST(Mitc4ShellKinematics, FormsDirectColumnsAndAllCovariantTyingSamples) {
const auto nodes = planarNodes();
const auto candidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(candidate.hasValue());
const auto& shell = candidate.value();
ASSERT_TRUE(candidate.HasValue());
const auto& shell = candidate.Value();
const auto direct = shell.directStrainDisplacement20(0.0, 0.0, 0.5);
ASSERT_EQ(direct.rows(), 5U);
ASSERT_EQ(direct.columns(), 20U);
ASSERT_EQ(direct.Rows(), 5U);
ASSERT_EQ(direct.Columns(), 20U);
EXPECT_DOUBLE_EQ(direct(0U, 0U), -0.25);
EXPECT_DOUBLE_EQ(direct(0U, 4U), -0.125);
EXPECT_DOUBLE_EQ(direct(1U, 1U), -0.25);
@@ -439,8 +439,8 @@ TEST(Mitc4ShellKinematics, FormsDirectColumnsAndAllCovariantTyingSamples) {
EXPECT_DOUBLE_EQ(direct(4U, 3U), -0.25);
const auto samples = shell.covariantTyingShearSamples20();
ASSERT_EQ(samples.rows(), 4U);
ASSERT_EQ(samples.columns(), 20U);
ASSERT_EQ(samples.Rows(), 4U);
ASSERT_EQ(samples.Columns(), 20U);
EXPECT_DOUBLE_EQ(samples(0U, 2U), -0.25);
EXPECT_DOUBLE_EQ(samples(0U, 4U), 0.25);
EXPECT_DOUBLE_EQ(samples(0U, 7U), 0.25);
@@ -476,21 +476,21 @@ TEST(Mitc4ShellConstitutive, BuildsExactPositiveDefiniteSectionMatricesAndRescal
const auto nodes = planarNodes();
const auto candidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(candidate.hasValue());
const auto& shell = candidate.value();
ASSERT_TRUE(candidate.HasValue());
const auto& shell = candidate.Value();
const auto cps = shell.planeStressConstitutive();
const auto c5 = shell.materialConstitutive5();
const auto a = shell.membraneSectionMatrix();
const auto d = shell.bendingSectionMatrix();
const auto as = shell.transverseShearSectionMatrix();
EXPECT_EQ(cps.rows(), 3U);
EXPECT_EQ(cps.columns(), 3U);
EXPECT_EQ(c5.rows(), 5U);
EXPECT_EQ(c5.columns(), 5U);
EXPECT_EQ(a.rows(), 3U);
EXPECT_EQ(d.rows(), 3U);
EXPECT_EQ(as.rows(), 2U);
EXPECT_EQ(cps.Rows(), 3U);
EXPECT_EQ(cps.Columns(), 3U);
EXPECT_EQ(c5.Rows(), 5U);
EXPECT_EQ(c5.Columns(), 5U);
EXPECT_EQ(a.Rows(), 3U);
EXPECT_EQ(d.Rows(), 3U);
EXPECT_EQ(as.Rows(), 2U);
EXPECT_DOUBLE_EQ(cps(0U, 0U), 128.0);
EXPECT_DOUBLE_EQ(cps(0U, 1U), 32.0);
EXPECT_DOUBLE_EQ(cps(2U, 2U), 48.0);
@@ -514,8 +514,8 @@ TEST(Mitc4ShellConstitutive, BuildsExactPositiveDefiniteSectionMatricesAndRescal
directors(),
section(2.0 * lengthScale),
material(120.0 * forceScale / (lengthScale * lengthScale), 0.25));
ASSERT_TRUE(scaledCandidate.hasValue());
const auto& scaled = scaledCandidate.value();
ASSERT_TRUE(scaledCandidate.HasValue());
const auto& scaled = scaledCandidate.Value();
fesa::Matrix expectedCps{3U, 3U};
fesa::Matrix expectedC5{5U, 5U};
fesa::Matrix expectedA{3U, 3U};
@@ -548,13 +548,13 @@ TEST(Mitc4ShellConstitutive, BuildsExactPositiveDefiniteSectionMatricesAndRescal
EXPECT_FALSE(fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(0.0), material())
.hasValue());
.HasValue());
EXPECT_FALSE(fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material(0.0, 0.25))
.hasValue());
.HasValue());
EXPECT_FALSE(fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material(120.0, 0.5))
.hasValue());
.HasValue());
}
// MITC4-KIN-005
@@ -578,23 +578,23 @@ TEST(Mitc4ShellKernel, FormsFiniteScaledSymmetricPhysicalAndStabilizedStiffness)
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
ASSERT_TRUE(shellCandidate.HasValue());
const auto stiffnessCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value();
EXPECT_EQ(stiffness.physicalLocal20.rows(), 20U);
EXPECT_EQ(stiffness.physicalLocal20.columns(), 20U);
EXPECT_EQ(stiffness.physicalGlobal24.rows(), 24U);
EXPECT_EQ(stiffness.drillingGlobal24.rows(), 24U);
EXPECT_EQ(stiffness.stabilizedGlobal24.rows(), 24U);
const auto stiffnessCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value();
EXPECT_EQ(stiffness.physicalLocal20.Rows(), 20U);
EXPECT_EQ(stiffness.physicalLocal20.Columns(), 20U);
EXPECT_EQ(stiffness.physicalGlobal24.Rows(), 24U);
EXPECT_EQ(stiffness.drillingGlobal24.Rows(), 24U);
EXPECT_EQ(stiffness.stabilizedGlobal24.Rows(), 24U);
for (const fesa::Matrix* matrix : {
&stiffness.physicalLocal20,
&stiffness.physicalGlobal24,
&stiffness.drillingGlobal24,
&stiffness.stabilizedGlobal24}) {
for (std::size_t row = 0U; row < matrix->rows(); ++row) {
for (std::size_t column = 0U; column < matrix->columns(); ++column) {
for (std::size_t row = 0U; row < matrix->Rows(); ++row) {
for (std::size_t column = 0U; column < matrix->Columns(); ++column) {
EXPECT_TRUE(std::isfinite((*matrix)(row, column)));
}
}
@@ -604,9 +604,9 @@ TEST(Mitc4ShellKernel, FormsFiniteScaledSymmetricPhysicalAndStabilizedStiffness)
EXPECT_LE(scaledSymmetryError(stiffness.drillingGlobal24, 6U, 2.0), 1.0e-12);
EXPECT_LE(scaledSymmetryError(stiffness.stabilizedGlobal24, 6U, 2.0), 1.0e-12);
const auto repeatedCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(repeatedCandidate.hasValue());
const auto& repeated = repeatedCandidate.value();
const auto repeatedCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(repeatedCandidate.HasValue());
const auto& repeated = repeatedCandidate.Value();
expectMatrixNear(repeated.physicalLocal20, stiffness.physicalLocal20, 0.0);
expectMatrixNear(repeated.physicalGlobal24, stiffness.physicalGlobal24, 0.0);
expectMatrixNear(repeated.drillingGlobal24, stiffness.drillingGlobal24, 0.0);
@@ -619,18 +619,18 @@ TEST(Mitc4ShellKernel, PreservesPhysicalEnergyUnderTwentyToTwentyFourCongruence)
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto stiffnessCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto stiffnessCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value();
fesa::Vector globalField{24U};
for (std::size_t index = 0U; index < globalField.size(); ++index) {
for (std::size_t index = 0U; index < globalField.Size(); ++index) {
globalField[index] = 0.125 * static_cast<double>(
static_cast<int>(index % 7U) - 3);
}
const auto physicalField20 =
shellCandidate.value().physicalTransformation20().multiply(globalField);
shellCandidate.Value().physicalTransformation20().Multiply(globalField);
const double localEnergy = quadraticEnergy(
stiffness.physicalLocal20, physicalField20);
const double globalEnergy = quadraticEnergy(
@@ -648,10 +648,10 @@ TEST(Mitc4ShellKernel, RetainsSixRigidModesAndHasExpectedPhysicalAndStabilizedRa
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto stiffnessCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto stiffnessCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value();
const auto scaledPhysical20 =
scaledStiffness(stiffness.physicalLocal20, 5U, 2.0);
@@ -680,14 +680,14 @@ TEST(Mitc4ShellKernel, RetainsSixRigidModesAndHasExpectedPhysicalAndStabilizedRa
scaledMode[6U * nodeIndex + component] /= 2.0;
}
}
const double modeNorm = scaledMode.norm();
const double modeNorm = scaledMode.Norm();
ASSERT_GT(modeNorm, 0.0);
EXPECT_LE(
scaledPhysical24.multiply(scaledMode).norm() /
scaledPhysical24.Multiply(scaledMode).Norm() /
(physicalNorm * modeNorm),
1.0e-10);
EXPECT_LE(
scaledStabilized24.multiply(scaledMode).norm() /
scaledStabilized24.Multiply(scaledMode).Norm() /
(stabilizedNorm * modeNorm),
1.0e-10);
}
@@ -698,11 +698,11 @@ TEST(Mitc4ShellPatch, ReproducesIndependentMembraneBendingShearAndTwistFields) {
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto& shell = shellCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto& shell = shellCandidate.Value();
const auto stiffnessCandidate = shell.stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value().physicalLocal20;
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value().physicalLocal20;
constexpr double magnitude = 0.2;
const double gauss = 1.0 / std::sqrt(3.0);
@@ -755,10 +755,10 @@ TEST(Mitc4ShellDrilling, UsesOnlyEightPositivePhysicalRotationDiagonalsAndFixedF
node(3, {50.0, 50.0, 0.0}), node(4, {-50.0, 50.0, 0.0})};
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(0.1), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto stiffnessCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto stiffnessCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value();
double expectedReference = (std::numeric_limits<double>::max)();
double allDiagonalMinimum = (std::numeric_limits<double>::max)();
@@ -788,17 +788,17 @@ TEST(Mitc4ShellDrilling, FailsNonfiniteReferenceAndStabilizesEachPureDrillCoordi
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto stiffnessCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto stiffnessCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value();
for (std::size_t nodeIndex = 0U; nodeIndex < 4U; ++nodeIndex) {
fesa::Vector pureDrill{24U};
pureDrill[6U * nodeIndex + 5U] = 1.0;
EXPECT_DOUBLE_EQ(
stiffness.physicalGlobal24.multiply(pureDrill).norm(), 0.0);
const auto drillAction = stiffness.drillingGlobal24.multiply(pureDrill);
stiffness.physicalGlobal24.Multiply(pureDrill).Norm(), 0.0);
const auto drillAction = stiffness.drillingGlobal24.Multiply(pureDrill);
EXPECT_DOUBLE_EQ(drillAction[6U * nodeIndex + 5U], stiffness.drillingStiffness);
EXPECT_GT(quadraticEnergy(stiffness.drillingGlobal24, pureDrill), 0.0);
}
@@ -808,20 +808,20 @@ TEST(Mitc4ShellDrilling, FailsNonfiniteReferenceAndStabilizesEachPureDrillCoordi
node(3, {5.0e9, 5.0e9, 0.0}), node(4, {-5.0e9, 5.0e9, 0.0})};
const auto extremeShell = fesa::Mitc4Shell::create(
nodePointers(extremeNodes), directors(), section(1.0), material(1.0e300));
ASSERT_TRUE(extremeShell.hasValue());
const auto failure = extremeShell.value().stiffness();
ASSERT_FALSE(failure.hasValue());
ASSERT_EQ(failure.status().diagnostics().size(), 1U);
EXPECT_EQ(failure.status().diagnostics()[0].code, "invalid-shell-stiffness");
const auto repeatedFailure = extremeShell.value().stiffness();
ASSERT_FALSE(repeatedFailure.hasValue());
ASSERT_EQ(repeatedFailure.status().diagnostics().size(), 1U);
ASSERT_TRUE(extremeShell.HasValue());
const auto failure = extremeShell.Value().stiffness();
ASSERT_FALSE(failure.HasValue());
ASSERT_EQ(failure.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(failure.GetStatus().Diagnostics()[0].code, "invalid-shell-stiffness");
const auto repeatedFailure = extremeShell.Value().stiffness();
ASSERT_FALSE(repeatedFailure.HasValue());
ASSERT_EQ(repeatedFailure.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
repeatedFailure.status().diagnostics()[0].code,
failure.status().diagnostics()[0].code);
repeatedFailure.GetStatus().Diagnostics()[0].code,
failure.GetStatus().Diagnostics()[0].code);
EXPECT_EQ(
repeatedFailure.status().diagnostics()[0].message,
failure.status().diagnostics()[0].message);
repeatedFailure.GetStatus().Diagnostics()[0].message,
failure.GetStatus().Diagnostics()[0].message);
}
// MITC4-KERNEL-007
@@ -829,21 +829,21 @@ TEST(Mitc4ShellDrilling, ExcludesPureDrillFromPhysicalRecoveryAndEnergy) {
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto& shell = shellCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto& shell = shellCandidate.Value();
const auto stiffnessCandidate = shell.stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
ASSERT_TRUE(stiffnessCandidate.HasValue());
for (std::size_t nodeIndex = 0U; nodeIndex < nodes.size(); ++nodeIndex) {
fesa::Vector pureDrill{24U};
pureDrill[6U * nodeIndex + 5U] = 1.0;
EXPECT_GT(
stiffnessCandidate.value().stabilizedGlobal24.multiply(pureDrill).norm(),
stiffnessCandidate.Value().stabilizedGlobal24.Multiply(pureDrill).Norm(),
0.0);
const auto recoveryCandidate = shell.recoverPhysical(pureDrill);
ASSERT_TRUE(recoveryCandidate.hasValue());
const auto& recovery = recoveryCandidate.value();
ASSERT_TRUE(recoveryCandidate.HasValue());
const auto& recovery = recoveryCandidate.Value();
EXPECT_DOUBLE_EQ(recovery.strainEnergy, 0.0);
for (const auto& point : recovery.points) {
for (double value : point.generalizedStrain) {
@@ -866,8 +866,8 @@ TEST(Mitc4ShellPhysicalRecovery, RecoversHandFieldAtFixedLocationsAndSectionPosi
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto& shell = shellCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto& shell = shellCandidate.Value();
constexpr std::array<double, 8> generalized{
0.1, -0.05, 0.2, 0.3, -0.15, 0.25, 0.4, -0.3};
@@ -889,8 +889,8 @@ TEST(Mitc4ShellPhysicalRecovery, RecoversHandFieldAtFixedLocationsAndSectionPosi
}
const auto recoveryCandidate = shell.recoverPhysical(globalField);
ASSERT_TRUE(recoveryCandidate.hasValue());
const auto& recovery = recoveryCandidate.value();
ASSERT_TRUE(recoveryCandidate.HasValue());
const auto& recovery = recoveryCandidate.Value();
const double gauss = 1.0 / std::sqrt(3.0);
const std::array<std::array<double, 2>, 4> expectedCoordinates{
std::array<double, 2>{-gauss, -gauss},
+21 -21
View File
@@ -78,12 +78,12 @@ struct DofFixture {
DofFixture makeDofFixture(fesa::ModelDefinition definition = makeDefinition()) {
auto domain = fesa::Domain::create(std::move(definition));
EXPECT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
EXPECT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
EXPECT_TRUE(dofs.hasValue());
return {std::move(dofs.value())};
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
EXPECT_TRUE(dofs.HasValue());
return {std::move(dofs.Value())};
}
std::vector<std::size_t> rowColumns(
@@ -122,7 +122,7 @@ TEST(DofManager, NumbersSixDofsAndFreeEquationsStably) {
TEST(DofManager, ExpandsAndValidatesPrescribedValues) {
const auto fixture = makeDofFixture();
const auto& values = fixture.dofs.prescribedValues();
ASSERT_EQ(values.size(), 5U);
ASSERT_EQ(values.Size(), 5U);
EXPECT_DOUBLE_EQ(values[0], 0.0);
EXPECT_DOUBLE_EQ(values[1], 0.0);
EXPECT_DOUBLE_EQ(values[2], 0.25);
@@ -133,18 +133,18 @@ TEST(DofManager, ExpandsAndValidatesPrescribedValues) {
conflictingDefinition.steps[0].boundaries.push_back(
{"root", 1, 1, 1.0, {conflictingDefinition.sourcePath, 77U}});
auto domain = fesa::Domain::create(std::move(conflictingDefinition));
ASSERT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
ASSERT_TRUE(model.hasValue());
ASSERT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
ASSERT_TRUE(model.HasValue());
auto conflict = fesa::DofManager::create(model.value());
ASSERT_FALSE(conflict.hasValue());
EXPECT_EQ(conflict.status().failureCategory(), fesa::FailureCategory::input);
ASSERT_EQ(conflict.status().diagnostics().size(), 1U);
const auto& diagnostic = conflict.status().diagnostics()[0];
auto conflict = fesa::DofManager::create(model.Value());
ASSERT_FALSE(conflict.HasValue());
EXPECT_EQ(conflict.GetStatus().Category(), fesa::FailureCategory::kInput);
ASSERT_EQ(conflict.GetStatus().Diagnostics().size(), 1U);
const auto& diagnostic = conflict.GetStatus().Diagnostics()[0];
EXPECT_EQ(diagnostic.code, "conflicting-boundary-condition");
EXPECT_EQ(diagnostic.keyword, "BOUNDARY");
EXPECT_EQ(diagnostic.entityIdentity, "root");
EXPECT_EQ(diagnostic.entity_identity, "root");
EXPECT_EQ(diagnostic.location.file, std::filesystem::path{"models/dof-manager.inp"});
EXPECT_EQ(diagnostic.location.line, 77U);
}
@@ -227,7 +227,7 @@ TEST(DofManager, ReconstructsFullReducedRoundTrip) {
const auto fixture = makeDofFixture();
const auto& dofs = fixture.dofs;
fesa::Vector full{dofs.fullDofCount()};
for (std::size_t index = 0U; index < full.size(); ++index) {
for (std::size_t index = 0U; index < full.Size(); ++index) {
full[index] = static_cast<double>(index) + 0.5;
}
for (std::size_t index = 0U; index < dofs.constrainedDofCount(); ++index) {
@@ -235,19 +235,19 @@ TEST(DofManager, ReconstructsFullReducedRoundTrip) {
}
fesa::Vector reduced{dofs.freeDofCount()};
for (std::size_t equation = 0U; equation < reduced.size(); ++equation) {
for (std::size_t equation = 0U; equation < reduced.Size(); ++equation) {
reduced[equation] = full[dofs.freeDofs()[equation]];
}
fesa::Vector reconstructed{dofs.fullDofCount()};
for (std::size_t equation = 0U; equation < reduced.size(); ++equation) {
for (std::size_t equation = 0U; equation < reduced.Size(); ++equation) {
reconstructed[dofs.freeDofs()[equation]] = reduced[equation];
}
for (std::size_t index = 0U; index < dofs.constrainedDofCount(); ++index) {
reconstructed[dofs.constrainedDofs()[index]] = dofs.prescribedValues()[index];
}
ASSERT_EQ(reconstructed.size(), full.size());
for (std::size_t index = 0U; index < full.size(); ++index) {
ASSERT_EQ(reconstructed.Size(), full.Size());
for (std::size_t index = 0U; index < full.Size(); ++index) {
EXPECT_DOUBLE_EQ(reconstructed[index], full[index]);
}
}
+161 -161
View File
@@ -45,10 +45,10 @@ fesa::Result<fesa::Domain> mapText(
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());
if (!parsed.HasValue()) {
return fesa::Result<fesa::Domain>::Failure(parsed.GetStatus());
}
return fesa::AbaqusDomainMapper{}.map(parsed.value());
return fesa::AbaqusDomainMapper{}.map(parsed.Value());
}
std::string readExactBytes(const std::filesystem::path& path) {
@@ -73,12 +73,12 @@ const fesa::Diagnostic* findDiagnostic(
const fesa::Status& status,
const std::string& code) {
const auto found = std::find_if(
status.diagnostics().begin(),
status.diagnostics().end(),
status.Diagnostics().begin(),
status.Diagnostics().end(),
[&code](const fesa::Diagnostic& diagnostic) {
return diagnostic.code == code;
});
return found == status.diagnostics().end() ? nullptr : &*found;
return found == status.Diagnostics().end() ? nullptr : &*found;
}
std::string replaceOnce(
@@ -246,18 +246,18 @@ RootAssembly, 6, -12.5
TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
auto result = mapText("supported-inventory", supportedInventoryDeck(true));
ASSERT_TRUE(result.hasValue());
const fesa::Domain& domain = result.value();
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_EQ(domain.nodes()[0].sourceId.instance_name, "Beam-1");
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 1);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "0001");
EXPECT_EQ(domain.nodes()[1].sourceId.source_label_text, "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].sourceId.source_label_text, "0007");
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U);
EXPECT_EQ(domain.elements()[0].nodeIndices[1], 1U);
@@ -304,15 +304,15 @@ TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
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);
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);
}
@@ -356,24 +356,24 @@ OnlySecond, 2, 5.
)inp";
auto result = mapText("multiple-instances", deck);
ASSERT_TRUE(result.hasValue());
const fesa::Domain& domain = result.value();
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);
EXPECT_EQ(domain.nodes()[0].sourceId.instance_name, "First");
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 10);
EXPECT_EQ(domain.nodes()[1].sourceId.instance_name, "First");
EXPECT_EQ(domain.nodes()[1].sourceId.source_label, 20);
EXPECT_EQ(domain.nodes()[2].sourceId.instance_name, "Second");
EXPECT_EQ(domain.nodes()[2].sourceId.source_label, 10);
EXPECT_EQ(domain.nodes()[3].sourceId.instance_name, "Second");
EXPECT_EQ(domain.nodes()[3].sourceId.source_label, 20);
ASSERT_EQ(domain.elements().size(), 2U);
EXPECT_EQ(domain.elements()[0].sourceId.instanceName, "First");
EXPECT_EQ(domain.elements()[0].sourceId.instance_name, "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].sourceId.instance_name, "Second");
EXPECT_EQ(domain.elements()[1].nodeIndices,
(std::array<fesa::EntityIndex, 2>{2U, 3U}));
@@ -412,9 +412,9 @@ OnlySecond, 2, 5.
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");
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",
@@ -422,7 +422,7 @@ OnlySecond, 2, 5.
replaceOnce(minimalDeck(), "2, 1., 0., 0.", "2, 2e-12, 0., 0."),
"0., 1., 0.",
"1., 2e-12, 0."));
ASSERT_TRUE(aboveThresholds.hasValue());
ASSERT_TRUE(aboveThresholds.HasValue());
auto largeFinite = mapText(
"large-finite-geometry",
@@ -433,7 +433,7 @@ OnlySecond, 2, 5.
"2, 1e308, 1e297, 0."),
"0., 1., 0.",
"1e308, 0., 0."));
ASSERT_TRUE(largeFinite.hasValue());
ASSERT_TRUE(largeFinite.HasValue());
}
TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
@@ -444,8 +444,8 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
"*Elset, elset=ShellS4\n10",
"*Nset, nset=ShellS4\n1, 2, 3, 4\n*Elset, elset=ShellS4\n10"));
ASSERT_TRUE(sharedName.hasValue());
const auto& domain = sharedName.value();
ASSERT_TRUE(sharedName.HasValue());
const auto& domain = sharedName.Value();
EXPECT_TRUE(std::any_of(
domain.nodeSets().begin(), domain.nodeSets().end(),
[](const fesa::NodeSet& set) { return set.name == "ShellS4"; }));
@@ -460,8 +460,8 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
"*Elset, elset=ShellS4\n10",
"*Nset, nset=Shared\n1\n*Nset, nset=Shared\n2\n"
"*Elset, elset=ShellS4\n10"));
ASSERT_FALSE(duplicateNodeSet.hasValue());
EXPECT_NE(findDiagnostic(duplicateNodeSet.status(), "duplicate-entity"), nullptr);
ASSERT_FALSE(duplicateNodeSet.HasValue());
EXPECT_NE(findDiagnostic(duplicateNodeSet.GetStatus(), "duplicate-entity"), nullptr);
auto duplicateElementSet = mapText(
"duplicate-part-element-set",
@@ -470,8 +470,8 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
"*Elset, elset=ShellS4\n10",
"*Elset, elset=Repeated\n10\n*Elset, elset=Repeated\n20\n"
"*Elset, elset=ShellS4\n10"));
ASSERT_FALSE(duplicateElementSet.hasValue());
EXPECT_NE(findDiagnostic(duplicateElementSet.status(), "duplicate-entity"), nullptr);
ASSERT_FALSE(duplicateElementSet.HasValue());
EXPECT_NE(findDiagnostic(duplicateElementSet.GetStatus(), "duplicate-entity"), nullptr);
auto assemblySharedName = mapText(
"separate-assembly-set-namespaces",
@@ -483,8 +483,8 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
"*Nset, nset=Root, instance=Beam-1\n1",
"*Nset, nset=Root, instance=Beam-1\n1\n"
"*Elset, elset=Root, instance=Beam-1\n1"));
ASSERT_TRUE(assemblySharedName.hasValue());
const auto& assemblyDomain = assemblySharedName.value();
ASSERT_TRUE(assemblySharedName.HasValue());
const auto& assemblyDomain = assemblySharedName.Value();
const auto rootNodeSetCount = std::count_if(
assemblyDomain.nodeSets().begin(), assemblyDomain.nodeSets().end(),
[](const fesa::NodeSet& set) { return set.name == "Root"; });
@@ -502,48 +502,48 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
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());
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,
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().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::kWarning);
}
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());
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());
}
// MITC4-MAP-001
TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
auto result = mapText("mitc4-map-001", shellDeck());
ASSERT_TRUE(result.hasValue());
const fesa::Domain& domain = result.value();
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
EXPECT_TRUE(domain.elements().empty());
ASSERT_EQ(domain.shellElements().size(), 4U);
EXPECT_EQ(domain.shellElements()[0].sourceId.instanceName, "First");
EXPECT_EQ(domain.shellElements()[0].sourceId.sourceLabelText, "0010");
EXPECT_EQ(domain.shellElements()[0].sourceId.instance_name, "First");
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label_text, "0010");
EXPECT_EQ(
domain.shellElements()[0].sourceType,
fesa::ShellSourceElementType::s4);
@@ -553,7 +553,7 @@ TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
EXPECT_EQ(domain.shellElements()[0].sectionIndex, 0U);
EXPECT_EQ(domain.shellElements()[0].materialIndex, 0U);
EXPECT_EQ(domain.shellElements()[1].sourceId.instanceName, "First");
EXPECT_EQ(domain.shellElements()[1].sourceId.instance_name, "First");
EXPECT_EQ(
domain.shellElements()[1].sourceType,
fesa::ShellSourceElementType::s4r);
@@ -563,11 +563,11 @@ TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
EXPECT_EQ(domain.shellElements()[1].sectionIndex, 1U);
EXPECT_EQ(domain.shellElements()[1].materialIndex, 1U);
EXPECT_EQ(domain.shellElements()[2].sourceId.instanceName, "Second");
EXPECT_EQ(domain.shellElements()[2].sourceId.instance_name, "Second");
EXPECT_EQ(
domain.shellElements()[2].nodeIndices,
(std::array<fesa::EntityIndex, 4>{6U, 7U, 8U, 9U}));
EXPECT_EQ(domain.shellElements()[3].sourceId.instanceName, "Second");
EXPECT_EQ(domain.shellElements()[3].sourceId.instance_name, "Second");
EXPECT_EQ(
fesa::kMitc4InternalFormulation,
std::string_view{"FESA-MITC4"});
@@ -612,34 +612,34 @@ TEST(InpDomainMapping, RejectsInvalidShellAssignmentsAndProperties) {
const std::vector<InvalidCase> cases{
{"unresolved-material",
replaceOnce(base, "material=Steel", "material=Missing"),
"unresolved-shell-section", fesa::FailureCategory::input},
"unresolved-shell-section", fesa::FailureCategory::kInput},
{"unresolved-elset",
replaceOnce(base, "elset=ShellS4, material=Steel",
"elset=Missing, material=Steel"),
"unresolved-shell-section", fesa::FailureCategory::input},
"unresolved-shell-section", fesa::FailureCategory::kInput},
{"missing-assignment",
replaceOnce(
base,
"*Shell Section, elset=ShellS4R, material=Aluminum\n0.2\n",
""),
"invalid-shell-section-assignment", fesa::FailureCategory::input},
"invalid-shell-section-assignment", fesa::FailureCategory::kInput},
{"conflicting-assignment",
replaceOnce(base, "elset=ShellS4R, material=Aluminum",
"elset=ShellS4, material=Aluminum"),
"invalid-shell-section-assignment", fesa::FailureCategory::input},
"invalid-shell-section-assignment", fesa::FailureCategory::kInput},
{"invalid-thickness",
replaceOnce(base, "0.2\n*End Part", "0.\n*End Part"),
"invalid-shell-thickness", fesa::FailureCategory::model},
"invalid-shell-thickness", fesa::FailureCategory::kModel},
{"invalid-material",
replaceOnce(base, "70000., 0.25", "70000., 0.5"),
"invalid-shell-material", fesa::FailureCategory::model}};
"invalid-shell-material", fesa::FailureCategory::kModel}};
for (const auto& testCase : cases) {
SCOPED_TRACE(testCase.name);
auto result = mapText("mitc4-map-002-" + testCase.name, testCase.deck);
ASSERT_FALSE(result.hasValue());
EXPECT_EQ(result.status().failureCategory(), testCase.category);
ASSERT_NE(findDiagnostic(result.status(), testCase.expectedCode), nullptr);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), testCase.category);
ASSERT_NE(findDiagnostic(result.GetStatus(), testCase.expectedCode), nullptr);
}
}
@@ -674,11 +674,11 @@ TEST(InpDomainMapping, RejectsInvalidShellConnectivityOptionsAndMixedModels) {
for (const auto& testCase : cases) {
SCOPED_TRACE(testCase.name);
auto result = mapText("mitc4-map-003-" + testCase.name, testCase.deck);
ASSERT_FALSE(result.hasValue());
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(
result.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_NE(findDiagnostic(result.status(), testCase.expectedCode), nullptr);
result.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_NE(findDiagnostic(result.GetStatus(), testCase.expectedCode), nullptr);
}
}
@@ -689,11 +689,11 @@ TEST(InpDomainMapping, PreservesProcedureLoadAndOutputRequestBoundariesForShells
"*End Step\n",
"*Output, field\n*Node Output\nU, RF\n*Element Output\nS\n*End Step\n");
auto valid = mapText("mitc4-map-004-no-ops", withNoOps);
ASSERT_TRUE(valid.hasValue());
ASSERT_EQ(valid.value().warnings().size(), 3U);
EXPECT_EQ(valid.value().warnings()[0].keyword, "OUTPUT");
EXPECT_EQ(valid.value().warnings()[1].keyword, "NODE OUTPUT");
EXPECT_EQ(valid.value().warnings()[2].keyword, "ELEMENT OUTPUT");
ASSERT_TRUE(valid.HasValue());
ASSERT_EQ(valid.Value().warnings().size(), 3U);
EXPECT_EQ(valid.Value().warnings()[0].keyword, "OUTPUT");
EXPECT_EQ(valid.Value().warnings()[1].keyword, "NODE OUTPUT");
EXPECT_EQ(valid.Value().warnings()[2].keyword, "ELEMENT OUTPUT");
struct InvalidCase {
std::string name;
@@ -720,11 +720,11 @@ TEST(InpDomainMapping, PreservesProcedureLoadAndOutputRequestBoundariesForShells
for (const auto& testCase : cases) {
SCOPED_TRACE(testCase.name);
auto result = mapText("mitc4-map-004-" + testCase.name, testCase.deck);
ASSERT_FALSE(result.hasValue());
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(
result.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_NE(findDiagnostic(result.status(), testCase.expectedCode), nullptr);
result.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_NE(findDiagnostic(result.GetStatus(), testCase.expectedCode), nullptr);
}
}
@@ -739,116 +739,116 @@ TEST(InpDomainMapping, RejectsUnsupportedAndInvalidPortfolio) {
const std::string base = minimalDeck();
const std::vector<InvalidCase> cases{
{"b31", replaceOnce(base, "type=B33", "type=B31"),
"unsupported-element-formulation", fesa::FailureCategory::input},
"unsupported-element-formulation", fesa::FailureCategory::kInput},
{"transform", replaceOnce(base, "*End Instance\n", "1., 0., 0.\n*End Instance\n"),
"unsupported-instance-transform", fesa::FailureCategory::input},
"unsupported-instance-transform", fesa::FailureCategory::kInput},
{"nested-assembly", replaceOnce(base, "*End Assembly\n", "*Assembly, name=Nested\n*End Assembly\n*End Assembly\n"),
"unsupported-nested-assembly", fesa::FailureCategory::input},
"unsupported-nested-assembly", fesa::FailureCategory::kInput},
{"multiple-step", base + "*Step\n*Static\n1., 1., 1., 1.\n*End Step\n",
"unsupported-multiple-step", fesa::FailureCategory::input},
"unsupported-multiple-step", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"keyword-after-step", base + "*Preprint, echo=NO\n",
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"cload-after-no-op", replaceOnce(base, "*Cload", "*Restart, write, frequency=0\n*Cload"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"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},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"dependent-instance", replaceOnce(base, "part=BeamPart", "part=BeamPart, dependent=YES"),
"unsupported-instance-mesh-semantics", fesa::FailureCategory::input},
"unsupported-instance-mesh-semantics", fesa::FailureCategory::kInput},
{"coupled-section", replaceOnce(base, "1., 1., 0., 1., 1.", "1., 1., 0.5, 1., 1."),
"unsupported-coupled-section", fesa::FailureCategory::model},
"unsupported-coupled-section", fesa::FailureCategory::kModel},
{"zero-length", replaceOnce(base, "2, 1., 0., 0.", "2, 0., 0., 0."),
"invalid-beam-length", fesa::FailureCategory::model},
"invalid-beam-length", fesa::FailureCategory::kModel},
{"parallel-guide", replaceOnce(base, "0., 1., 0.", "1., 0., 0."),
"invalid-beam-guide-vector", fesa::FailureCategory::model},
"invalid-beam-guide-vector", fesa::FailureCategory::kModel},
{"nonpositive-area", replaceOnce(base, "1., 1., 0., 1., 1.", "0., 1., 0., 1., 1."),
"invalid-beam-property", fesa::FailureCategory::model},
"invalid-beam-property", fesa::FailureCategory::kModel},
{"nonpositive-derived-shear", replaceOnce(base, "100., 0.25", "100., -1.25"),
"invalid-beam-property", fesa::FailureCategory::model},
"invalid-beam-property", fesa::FailureCategory::kModel},
{"nonfinite-elastic", replaceOnce(base, "100., 0.25", "inf, 0.25"),
"invalid-beam-property", fesa::FailureCategory::model},
"invalid-beam-property", fesa::FailureCategory::kModel},
{"nonfinite-section-property", replaceOnce(base, "1., 1., 0., 1., 1.", "nan, 1., 0., 1., 1."),
"invalid-beam-property", fesa::FailureCategory::model},
"invalid-beam-property", fesa::FailureCategory::kModel},
{"nonfinite-guide", replaceOnce(base, "0., 1., 0.", "0., inf, 0."),
"invalid-beam-guide-vector", fesa::FailureCategory::model},
"invalid-beam-guide-vector", fesa::FailureCategory::kModel},
{"length-at-threshold", replaceOnce(base, "2, 1., 0., 0.", "2, 1e-12, 0., 0."),
"invalid-beam-length", fesa::FailureCategory::model},
"invalid-beam-length", fesa::FailureCategory::kModel},
{"guide-at-threshold", replaceOnce(base, "0., 1., 0.", "1., 1e-12, 0."),
"invalid-beam-guide-vector", fesa::FailureCategory::model},
"invalid-beam-guide-vector", fesa::FailureCategory::kModel},
{"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-entity", fesa::FailureCategory::kInput},
{"duplicate-node-label", replaceOnce(base, "2, 1., 0., 0.", "1, 1., 0., 0."),
"duplicate-entity", fesa::FailureCategory::input},
"duplicate-entity", fesa::FailureCategory::kInput},
{"dangling-connectivity", replaceOnce(base, "1, 1, 2", "1, 1, 9"),
"unresolved-reference", fesa::FailureCategory::input},
"unresolved-reference", fesa::FailureCategory::kInput},
{"invalid-dof", replaceOnce(base, "Root, 1, 6", "Root, 1, 7"),
"invalid-dof", fesa::FailureCategory::input},
"invalid-dof", fesa::FailureCategory::kInput},
{"nonfinite-coordinate", replaceOnce(base, "1., 0., 0.", "nan, 0., 0."),
"invalid-numeric-value", fesa::FailureCategory::input},
"invalid-numeric-value", fesa::FailureCategory::kInput},
{"malformed-node-arity", replaceOnce(base, "1, 0., 0., 0.", "1, 0., 0."),
"invalid-data-arity", fesa::FailureCategory::input},
"invalid-data-arity", fesa::FailureCategory::kInput},
{"nlgeom", replaceOnce(base, "nlgeom=NO", "nlgeom=YES"),
"unsupported-nonlinear-geometry", fesa::FailureCategory::model},
"unsupported-nonlinear-geometry", fesa::FailureCategory::kModel},
{"unknown-keyword", replaceOnce(base, "*Assembly, name=Assembly", "*Density\n1.\n*Assembly, name=Assembly"),
"unsupported-keyword", fesa::FailureCategory::input},
"unsupported-keyword", fesa::FailureCategory::kInput},
{"invalid-static-arity", replaceOnce(base, "0.1, 1., 0.01, 1.", "0.1, 1., 0.01"),
"invalid-static-data", fesa::FailureCategory::input},
"invalid-static-data", fesa::FailureCategory::kInput},
{"invalid-static-range", replaceOnce(base, "0.1, 1., 0.01, 1.", "0.1, 1., 2., 1."),
"invalid-static-data", fesa::FailureCategory::input},
"invalid-static-data", fesa::FailureCategory::kInput},
{"invalid-generate", replaceOnce(base, "*Elset, elset=BeamSet\n1", "*Elset, elset=BeamSet, generate\n1, 1, 0"),
"invalid-set-range", fesa::FailureCategory::input},
"invalid-set-range", fesa::FailureCategory::kInput},
{"nonlanding-generate", replaceOnce(base, "*Elset, elset=BeamSet\n1", "*Elset, elset=BeamSet, generate\n1, 2, 2"),
"invalid-set-range", fesa::FailureCategory::input},
"invalid-set-range", fesa::FailureCategory::kInput},
{"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},
"unresolved-reference", fesa::FailureCategory::kInput},
{"ambiguous-part-set", replaceOnce(
replaceOnce(
replaceOnce(base,
@@ -858,24 +858,24 @@ TEST(InpDomainMapping, RejectsUnsupportedAndInvalidPortfolio) {
"*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},
"unresolved-reference", fesa::FailureCategory::kInput},
{"direct-set-conflict", replaceOnce(base,
"*Step, name=Load",
"*Boundary\n1, 1, 1, 2.\n*Step, name=Load"),
"conflicting-boundary-condition", fesa::FailureCategory::input},
"conflicting-boundary-condition", fesa::FailureCategory::kInput},
{"dangling-boundary-target", replaceOnce(base, "Root, 1, 6", "Missing, 1, 6"),
"unresolved-reference", fesa::FailureCategory::input},
"unresolved-reference", fesa::FailureCategory::kInput},
{"conflicting-boundary", replaceOnce(base, "*Step, name=Load", "*Boundary\nRoot, 1, 1, 2.\n*Step, name=Load"),
"conflicting-boundary-condition", fesa::FailureCategory::input}};
"conflicting-boundary-condition", fesa::FailureCategory::kInput}};
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_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), testCase.category);
const auto* diagnostic = findDiagnostic(result.GetStatus(), testCase.expectedCode);
ASSERT_NE(diagnostic, nullptr);
EXPECT_EQ(diagnostic->severity, fesa::Severity::error);
EXPECT_EQ(diagnostic->severity, fesa::Severity::kError);
EXPECT_FALSE(diagnostic->location.file.empty());
EXPECT_GT(diagnostic->location.line, 0U);
}
@@ -899,16 +899,16 @@ TEST(InpDomainMapping, RejectsUnsupportedAndInvalidPortfolio) {
for (const auto& testCase : sameTokenCases) {
SCOPED_TRACE("same-token-" + testCase.name);
auto result = mapText("same-token-" + testCase.name, testCase.deck);
ASSERT_FALSE(result.hasValue());
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(
result.status().failureCategory(),
fesa::FailureCategory::input);
result.GetStatus().Category(),
fesa::FailureCategory::kInput);
const auto* diagnostic =
findDiagnostic(result.status(), "unresolved-reference");
findDiagnostic(result.GetStatus(), "unresolved-reference");
ASSERT_NE(diagnostic, nullptr);
EXPECT_EQ(diagnostic->severity, fesa::Severity::error);
EXPECT_EQ(diagnostic->severity, fesa::Severity::kError);
EXPECT_EQ(diagnostic->keyword, testCase.expectedKeyword);
EXPECT_EQ(diagnostic->entityIdentity, "1");
EXPECT_EQ(diagnostic->entity_identity, "1");
EXPECT_EQ(diagnostic->location.line, testCase.expectedLine);
EXPECT_EQ(
diagnostic->location.file.filename().string(),
@@ -923,9 +923,9 @@ TEST(InpDomainMapping, RejectsDloadWithoutDistributedLoadObject) {
"*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_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kInput);
const auto* diagnostic = findDiagnostic(result.GetStatus(), "unsupported-keyword");
ASSERT_NE(diagnostic, nullptr);
EXPECT_EQ(diagnostic->keyword, "DLOAD");
}
+27 -27
View File
@@ -63,37 +63,37 @@ TEST(InpSyntax, RejectsMalformedOrOrphanData) {
std::filesystem::remove(missingPath, removeError);
const auto unreadable = fesa::AbaqusInputReader{}.read(missingPath);
ASSERT_FALSE(unreadable.hasValue());
ASSERT_FALSE(unreadable.HasValue());
EXPECT_EQ(
unreadable.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_EQ(unreadable.status().diagnostics().size(), 1U);
EXPECT_EQ(unreadable.status().diagnostics()[0].code,
unreadable.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_EQ(unreadable.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(unreadable.GetStatus().Diagnostics()[0].code,
"input-file-unreadable");
const TemporaryInputFile malformed{"malformed-keyword", "*, name=value\n"};
const auto malformedResult =
fesa::AbaqusInputReader{}.read(malformed.path());
ASSERT_FALSE(malformedResult.hasValue());
ASSERT_FALSE(malformedResult.HasValue());
EXPECT_EQ(
malformedResult.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_EQ(malformedResult.status().diagnostics().size(), 1U);
EXPECT_EQ(malformedResult.status().diagnostics()[0].code,
malformedResult.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_EQ(malformedResult.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(malformedResult.GetStatus().Diagnostics()[0].code,
"malformed-keyword");
EXPECT_EQ(malformedResult.status().diagnostics()[0].location.line, 1U);
EXPECT_EQ(malformedResult.GetStatus().Diagnostics()[0].location.line, 1U);
const TemporaryInputFile orphan{
"orphan-data", "** comment\n\norphan, data\n"};
const auto orphanResult = fesa::AbaqusInputReader{}.read(orphan.path());
ASSERT_FALSE(orphanResult.hasValue());
ASSERT_FALSE(orphanResult.HasValue());
EXPECT_EQ(
orphanResult.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_EQ(orphanResult.status().diagnostics().size(), 1U);
EXPECT_EQ(orphanResult.status().diagnostics()[0].code,
orphanResult.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_EQ(orphanResult.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(orphanResult.GetStatus().Diagnostics()[0].code,
"orphan-data-line");
EXPECT_EQ(orphanResult.status().diagnostics()[0].location.line, 3U);
EXPECT_EQ(orphanResult.GetStatus().Diagnostics()[0].location.line, 3U);
}
TEST(InpSyntax, ReadsLegacyCantileverWithoutMutation) {
@@ -105,23 +105,23 @@ TEST(InpSyntax, ReadsLegacyCantileverWithoutMutation) {
const auto result = fesa::AbaqusInputReader{}.read(inputPath);
ASSERT_TRUE(result.hasValue());
EXPECT_EQ(result.value().sourceContentIdentity,
ASSERT_TRUE(result.HasValue());
EXPECT_EQ(result.Value().sourceContentIdentity,
"fnv1a64:04543464cc970405");
EXPECT_EQ(result.value().sourcePath,
EXPECT_EQ(result.Value().sourcePath,
std::filesystem::absolute(inputPath).lexically_normal());
ASSERT_EQ(result.value().blocks.size(), 30U);
EXPECT_EQ(result.value().blocks.front().canonicalName, "HEADING");
EXPECT_EQ(result.value().blocks.front().location.line, 1U);
EXPECT_EQ(result.value().blocks.back().canonicalName, "END STEP");
ASSERT_EQ(result.Value().blocks.size(), 30U);
EXPECT_EQ(result.Value().blocks.front().canonicalName, "HEADING");
EXPECT_EQ(result.Value().blocks.front().location.line, 1U);
EXPECT_EQ(result.Value().blocks.back().canonicalName, "END STEP");
const auto element = std::find_if(
result.value().blocks.begin(),
result.value().blocks.end(),
result.Value().blocks.begin(),
result.Value().blocks.end(),
[](const fesa::KeywordBlock& block) {
return block.canonicalName == "ELEMENT";
});
ASSERT_NE(element, result.value().blocks.end());
ASSERT_NE(element, result.Value().blocks.end());
ASSERT_EQ(element->parameters.size(), 1U);
EXPECT_EQ(element->parameters[0].name, "TYPE");
ASSERT_TRUE(element->parameters[0].value.has_value());
+10 -10
View File
@@ -45,9 +45,9 @@ TEST(InpSyntax, CanonicalizesKeywordAndParameterNamesOnly) {
const auto result = fesa::AbaqusInputReader{}.read(input.path());
ASSERT_TRUE(result.hasValue());
ASSERT_EQ(result.value().blocks.size(), 1U);
const auto& block = result.value().blocks[0];
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(result.Value().blocks.size(), 1U);
const auto& block = result.Value().blocks[0];
EXPECT_EQ(block.canonicalName, "ELEMENT");
EXPECT_EQ(block.originalLine, originalLine);
EXPECT_EQ(block.location.line, 1U);
@@ -73,23 +73,23 @@ TEST(InpSyntax, PreservesDataAndSourceLocations) {
const auto result = fesa::AbaqusInputReader{}.read(input.path());
ASSERT_TRUE(result.hasValue());
ASSERT_TRUE(result.HasValue());
EXPECT_EQ(
result.value().sourcePath,
result.Value().sourcePath,
std::filesystem::absolute(input.path()).lexically_normal());
EXPECT_EQ(result.value().sourceContentIdentity,
EXPECT_EQ(result.Value().sourceContentIdentity,
"fnv1a64:c120b6ed2445be46");
ASSERT_EQ(result.value().blocks.size(), 1U);
const auto& block = result.value().blocks[0];
ASSERT_EQ(result.Value().blocks.size(), 1U);
const auto& block = result.Value().blocks[0];
EXPECT_EQ(block.canonicalName, "NODE");
EXPECT_EQ(block.originalLine, "*NoDe");
EXPECT_EQ(block.location.file, result.value().sourcePath);
EXPECT_EQ(block.location.file, result.Value().sourcePath);
EXPECT_EQ(block.location.line, 3U);
ASSERT_EQ(block.data.size(), 1U);
EXPECT_EQ(
block.data[0].fields,
(std::vector<std::string>{"0007", "Label_A", "", ""}));
EXPECT_EQ(block.data[0].location.file, result.value().sourcePath);
EXPECT_EQ(block.data[0].location.file, result.Value().sourcePath);
EXPECT_EQ(block.data[0].location.line, 4U);
}
+33 -33
View File
@@ -5,7 +5,7 @@
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/analysis/analysis_state.hpp"
#include "fesa/build_info.hpp"
#include "fesa/build_info.h"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/model/domain.hpp"
@@ -158,27 +158,27 @@ WriterFixture makeFixture(
const bool useDefaultCentroid = false) {
auto domainResult = fesa::Domain::create(
makeDefinition(source, useDefaultCentroid));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{"Writer fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Writer fixture AnalysisModel construction failed."};
}
const fesa::AnalysisModel model = std::move(modelResult.value());
const fesa::AnalysisModel model = std::move(modelResult.Value());
auto dofsResult = fesa::DofManager::create(model);
if (!dofsResult.hasValue()) {
if (!dofsResult.HasValue()) {
throw std::runtime_error{"Writer fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofsResult.value()));
std::move(dofsResult.Value()));
auto state = std::make_unique<fesa::AnalysisState>(
fesa::AnalysisState::create(*dofs, {"Step-1", 0U}));
for (std::size_t index = 0U; index < state->displacement().size(); ++index) {
for (std::size_t index = 0U; index < state->displacement().Size(); ++index) {
state->displacement()[index] = 0.25 + static_cast<double>(index);
state->externalForce()[index] = 100.0 + static_cast<double>(index);
state->internalForce()[index] = 200.0 + 2.0 * static_cast<double>(index);
@@ -259,25 +259,25 @@ fesa::ModelDefinition makeShellDefinition(const std::filesystem::path& source) {
WriterFixture makeShellFixture(const std::filesystem::path& source) {
auto domainResult = fesa::Domain::create(makeShellDefinition(source));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{"Shell writer fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Shell writer fixture AnalysisModel construction failed."};
}
const fesa::AnalysisModel model = std::move(modelResult.value());
const fesa::AnalysisModel model = std::move(modelResult.Value());
auto dofsResult = fesa::DofManager::create(model);
if (!dofsResult.hasValue()) {
if (!dofsResult.HasValue()) {
throw std::runtime_error{"Shell writer fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofsResult.value()));
std::move(dofsResult.Value()));
auto state = std::make_unique<fesa::AnalysisState>(
fesa::AnalysisState::create(*dofs, {"Step-1", 0U}));
for (std::size_t index = 0U; index < state->displacement().size(); ++index) {
for (std::size_t index = 0U; index < state->displacement().Size(); ++index) {
state->displacement()[index] = 0.01 * static_cast<double>(index + 1U);
state->externalForce()[index] = 10.0 + static_cast<double>(index);
state->internalForce()[index] = 20.0 + static_cast<double>(index);
@@ -325,7 +325,7 @@ WriterFixture makeShellFixture(const std::filesystem::path& source) {
candidate.verificationMetrics = {1.0e-13, 2.0e-13, 3.0e-13};
const fesa::Status commit = state->commitShellResults(
{0U}, std::move(candidate));
if (!commit.isOk()) {
if (!commit.IsOk()) {
throw std::runtime_error{"Shell writer fixture state commit failed."};
}
return {std::move(domain), std::move(dofs), std::move(state)};
@@ -830,11 +830,11 @@ std::size_t entryCount(const std::filesystem::path& directory) {
void expectOutputFailure(
const fesa::Status& status, const std::string& expectedCode) {
ASSERT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::output);
ASSERT_EQ(status.diagnostics().size(), 1U);
EXPECT_EQ(status.diagnostics()[0U].severity, fesa::Severity::error);
EXPECT_EQ(status.diagnostics()[0U].code, expectedCode);
ASSERT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kOutput);
ASSERT_EQ(status.Diagnostics().size(), 1U);
EXPECT_EQ(status.Diagnostics()[0U].severity, fesa::Severity::kError);
EXPECT_EQ(status.Diagnostics()[0U].code, expectedCode);
}
} // namespace
@@ -846,7 +846,7 @@ TEST(Hdf5ResultsWriter, WritesExactSchemaShapesAttributesAndIdentity) {
const auto output = directory.path() / "results.h5";
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).isOk());
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
const auto file = openFile(output);
@@ -874,7 +874,7 @@ TEST(Hdf5ResultsWriter, WritesExactSchemaShapesAttributesAndIdentity) {
"linear-static-3d-euler-beam");
EXPECT_EQ(
readStringAttribute(metadata.get(), "solver_version"),
std::string{fesa::solverVersion()});
std::string{fesa::SolverVersion()});
const std::string normalizedSource =
std::filesystem::absolute(source).lexically_normal().generic_u8string();
EXPECT_EQ(
@@ -1031,7 +1031,7 @@ TEST(Hdf5ResultsWriter, WritesMandatoryOutputsDespiteOutputRequests) {
TempDirectory directory{"mandatory"};
auto fixture = makeFixture(directory.path() / "request-model.inp");
const fesa::Diagnostic ignoredRequest{
fesa::Severity::warning,
fesa::Severity::kWarning,
"ignored-output-request",
{fixture.domain->sourcePath(), 70U},
"*OUTPUT",
@@ -1042,7 +1042,7 @@ TEST(Hdf5ResultsWriter, WritesMandatoryOutputsDespiteOutputRequests) {
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(
writer.write(output, *fixture.domain, *fixture.state, {ignoredRequest})
.isOk());
.IsOk());
const auto file = openFile(output);
for (const char* suffix : {
"/nodal/displacement",
@@ -1063,13 +1063,13 @@ TEST(Hdf5ResultsWriter, WritesWarningsAndDefaultCentroid) {
TempDirectory directory{"warnings"};
auto fixture = makeFixture(directory.path() / "centroid.inp", true);
std::vector<fesa::Diagnostic> diagnostics = {
{fesa::Severity::warning,
{fesa::Severity::kWarning,
"ignored-output-request",
{fixture.domain->sourcePath(), 80U},
"*OUTPUT",
"FIELD",
"Ignored output request."},
{fesa::Severity::warning,
{fesa::Severity::kWarning,
"ignored-keyword",
{fixture.domain->sourcePath(), 20U},
"*PREPRINT",
@@ -1078,7 +1078,7 @@ TEST(Hdf5ResultsWriter, WritesWarningsAndDefaultCentroid) {
const auto output = directory.path() / "results.h5";
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, diagnostics).isOk());
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, diagnostics).IsOk());
const auto file = openFile(output);
auto stressRows = readStressRows(file.get());
ASSERT_EQ(stressRows.size(), 2U);
@@ -1153,7 +1153,7 @@ TEST(Hdf5ResultsWriter, SuccessfullyReplacesExistingFinal) {
writeBytes(final, {'o', 'l', 'd'});
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(writer.write(final, *fixture.domain, *fixture.state, {}).isOk());
ASSERT_TRUE(writer.write(final, *fixture.domain, *fixture.state, {}).IsOk());
EXPECT_GT(H5Fis_hdf5(final.string().c_str()), 0);
EXPECT_EQ(entryCount(directory.path()), 1U);
const auto file = openFile(final);
@@ -1171,7 +1171,7 @@ TEST(Hdf5ResultsWriter, WritesExactShellMetadataAndModelIdentity) {
const auto output = directory.path() / "results.h5";
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).isOk());
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
const auto file = openFile(output);
Hdf5Handle metadata{
@@ -1261,7 +1261,7 @@ TEST(Hdf5ResultsWriter, WritesExactMandatoryShellResultInventory) {
const auto output = directory.path() / "results.h5";
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).isOk());
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
const auto file = openFile(output);
const std::string shellRoot = std::string{kStepRoot} + "/element/shell";
expectNumericDataset(
@@ -1319,7 +1319,7 @@ TEST(Hdf5ResultsWriter, WritesShellInventoryDespiteRequestsAndOmitsForbiddenPath
TempDirectory directory{"shell-mandatory"};
auto fixture = makeShellFixture(directory.path() / "shell.inp");
const fesa::Diagnostic ignoredRequest{
fesa::Severity::warning,
fesa::Severity::kWarning,
"ignored-output-request",
{fixture.domain->sourcePath(), 80U},
"*ELEMENT OUTPUT",
@@ -1330,7 +1330,7 @@ TEST(Hdf5ResultsWriter, WritesShellInventoryDespiteRequestsAndOmitsForbiddenPath
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(
writer.write(output, *fixture.domain, *fixture.state, {ignoredRequest})
.isOk());
.IsOk());
const auto file = openFile(output);
for (const char* suffix : {
"/element/shell/local_frame",
+87 -83
View File
@@ -1,4 +1,4 @@
#include "fesa/math/matrix.hpp"
#include "fesa/math/matrix.h"
#include <gtest/gtest.h>
@@ -10,100 +10,104 @@ namespace fesa {
namespace {
TEST(DenseMath, RowMajorMatrixMatchesKnownGemvGemm) {
const std::size_t wraparoundRows =
(std::numeric_limits<std::size_t>::max)() / 2U + 1U;
EXPECT_THROW(static_cast<void>(Matrix{wraparoundRows, 2}), std::length_error);
const std::size_t wraparound_rows =
(std::numeric_limits<std::size_t>::max)() / 2U + 1U;
EXPECT_THROW(static_cast<void>(Matrix{wraparound_rows, 2}),
std::length_error);
Matrix zeroRows{0, 3};
Vector threeValues{3, 2.0};
const Vector zeroRowProduct = zeroRows.multiply(threeValues);
EXPECT_EQ(zeroRowProduct.size(), 0U);
Matrix zero_rows{0, 3};
Vector three_values{3, 2.0};
const Vector zero_row_product = zero_rows.Multiply(three_values);
EXPECT_EQ(zero_row_product.Size(), 0U);
Matrix zeroColumns{2, 0};
const Vector zeroColumnProduct = zeroColumns.multiply(Vector{0});
ASSERT_EQ(zeroColumnProduct.size(), 2U);
EXPECT_DOUBLE_EQ(zeroColumnProduct[0], 0.0);
EXPECT_DOUBLE_EQ(zeroColumnProduct[1], 0.0);
Matrix zero_columns{2, 0};
const Vector zero_column_product = zero_columns.Multiply(Vector{0});
ASSERT_EQ(zero_column_product.Size(), 2U);
EXPECT_DOUBLE_EQ(zero_column_product[0], 0.0);
EXPECT_DOUBLE_EQ(zero_column_product[1], 0.0);
Matrix zeroInnerRight{0, 3};
const Matrix zeroInnerProduct = zeroColumns.multiply(zeroInnerRight);
EXPECT_EQ(zeroInnerProduct.rows(), 2U);
EXPECT_EQ(zeroInnerProduct.columns(), 3U);
for (std::size_t row = 0; row < zeroInnerProduct.rows(); ++row) {
for (std::size_t column = 0; column < zeroInnerProduct.columns(); ++column) {
EXPECT_DOUBLE_EQ(zeroInnerProduct(row, column), 0.0);
}
Matrix zero_inner_right{0, 3};
const Matrix zero_inner_product = zero_columns.Multiply(zero_inner_right);
EXPECT_EQ(zero_inner_product.Rows(), 2U);
EXPECT_EQ(zero_inner_product.Columns(), 3U);
for (std::size_t row = 0; row < zero_inner_product.Rows(); ++row) {
for (std::size_t column = 0; column < zero_inner_product.Columns();
++column) {
EXPECT_DOUBLE_EQ(zero_inner_product(row, column), 0.0);
}
}
Matrix left{2, 3};
left(0, 0) = 1.0;
left(0, 1) = 2.0;
left(0, 2) = 3.0;
left(1, 0) = 4.0;
left(1, 1) = 5.0;
left(1, 2) = 6.0;
Matrix left{2, 3};
left(0, 0) = 1.0;
left(0, 1) = 2.0;
left(0, 2) = 3.0;
left(1, 0) = 4.0;
left(1, 1) = 5.0;
left(1, 2) = 6.0;
EXPECT_EQ(&left(0, 0) + 1, &left(0, 1));
EXPECT_EQ(&left(0, 0) + 2, &left(0, 2));
EXPECT_EQ(&left(0, 0) + 3, &left(1, 0));
const Matrix& constLeft = left;
EXPECT_DOUBLE_EQ(constLeft(1, 2), 6.0);
EXPECT_EQ(&left(0, 0) + 1, &left(0, 1));
EXPECT_EQ(&left(0, 0) + 2, &left(0, 2));
EXPECT_EQ(&left(0, 0) + 3, &left(1, 0));
const Matrix& const_left = left;
EXPECT_DOUBLE_EQ(const_left(1, 2), 6.0);
Matrix copied{left};
copied(0, 0) = 42.0;
EXPECT_DOUBLE_EQ(left(0, 0), 1.0);
Matrix copied{left};
copied(0, 0) = 42.0;
EXPECT_DOUBLE_EQ(left(0, 0), 1.0);
Matrix copyAssigned{0, 0};
copyAssigned = left;
copyAssigned(1, 2) = -7.0;
EXPECT_DOUBLE_EQ(left(1, 2), 6.0);
Matrix copy_assigned{0, 0};
copy_assigned = left;
copy_assigned(1, 2) = -7.0;
EXPECT_DOUBLE_EQ(left(1, 2), 6.0);
Matrix moved{std::move(copied)};
EXPECT_EQ(copied.rows(), 0U);
EXPECT_EQ(copied.columns(), 0U);
EXPECT_EQ(moved.rows(), 2U);
EXPECT_EQ(moved.columns(), 3U);
EXPECT_DOUBLE_EQ(moved(0, 0), 42.0);
EXPECT_NO_THROW(static_cast<void>(copied.multiply(Vector{0})));
Matrix moved{std::move(copied)};
EXPECT_EQ(copied.Rows(), 0U);
EXPECT_EQ(copied.Columns(), 0U);
EXPECT_EQ(moved.Rows(), 2U);
EXPECT_EQ(moved.Columns(), 3U);
EXPECT_DOUBLE_EQ(moved(0, 0), 42.0);
EXPECT_NO_THROW(static_cast<void>(copied.Multiply(Vector{0})));
Matrix moveAssigned{1, 1, -1.0};
moveAssigned = std::move(copyAssigned);
EXPECT_EQ(copyAssigned.rows(), 0U);
EXPECT_EQ(copyAssigned.columns(), 0U);
EXPECT_EQ(moveAssigned.rows(), 2U);
EXPECT_EQ(moveAssigned.columns(), 3U);
EXPECT_DOUBLE_EQ(moveAssigned(1, 2), -7.0);
Matrix move_assigned{1, 1, -1.0};
move_assigned = std::move(copy_assigned);
EXPECT_EQ(copy_assigned.Rows(), 0U);
EXPECT_EQ(copy_assigned.Columns(), 0U);
EXPECT_EQ(move_assigned.Rows(), 2U);
EXPECT_EQ(move_assigned.Columns(), 3U);
EXPECT_DOUBLE_EQ(move_assigned(1, 2), -7.0);
Vector vector{3};
vector[0] = 7.0;
vector[1] = 8.0;
vector[2] = 9.0;
const Vector matrixVectorProduct = left.multiply(vector);
ASSERT_EQ(matrixVectorProduct.size(), 2U);
EXPECT_DOUBLE_EQ(matrixVectorProduct[0], 50.0);
EXPECT_DOUBLE_EQ(matrixVectorProduct[1], 122.0);
Vector vector{3};
vector[0] = 7.0;
vector[1] = 8.0;
vector[2] = 9.0;
const Vector matrix_vector_product = left.Multiply(vector);
ASSERT_EQ(matrix_vector_product.Size(), 2U);
EXPECT_DOUBLE_EQ(matrix_vector_product[0], 50.0);
EXPECT_DOUBLE_EQ(matrix_vector_product[1], 122.0);
Matrix right{3, 2};
right(0, 0) = 7.0;
right(0, 1) = 8.0;
right(1, 0) = 9.0;
right(1, 1) = 10.0;
right(2, 0) = 11.0;
right(2, 1) = 12.0;
const Matrix matrixProduct = left.multiply(right);
ASSERT_EQ(matrixProduct.rows(), 2U);
ASSERT_EQ(matrixProduct.columns(), 2U);
EXPECT_DOUBLE_EQ(matrixProduct(0, 0), 58.0);
EXPECT_DOUBLE_EQ(matrixProduct(0, 1), 64.0);
EXPECT_DOUBLE_EQ(matrixProduct(1, 0), 139.0);
EXPECT_DOUBLE_EQ(matrixProduct(1, 1), 154.0);
Matrix right{3, 2};
right(0, 0) = 7.0;
right(0, 1) = 8.0;
right(1, 0) = 9.0;
right(1, 1) = 10.0;
right(2, 0) = 11.0;
right(2, 1) = 12.0;
const Matrix matrix_product = left.Multiply(right);
ASSERT_EQ(matrix_product.Rows(), 2U);
ASSERT_EQ(matrix_product.Columns(), 2U);
EXPECT_DOUBLE_EQ(matrix_product(0, 0), 58.0);
EXPECT_DOUBLE_EQ(matrix_product(0, 1), 64.0);
EXPECT_DOUBLE_EQ(matrix_product(1, 0), 139.0);
EXPECT_DOUBLE_EQ(matrix_product(1, 1), 154.0);
EXPECT_THROW(static_cast<void>(left(2, 0)), std::out_of_range);
EXPECT_THROW(static_cast<void>(left(0, 3)), std::out_of_range);
EXPECT_THROW(static_cast<void>(constLeft(2, 0)), std::out_of_range);
EXPECT_THROW(static_cast<void>(left.multiply(Vector{2})), std::invalid_argument);
EXPECT_THROW(static_cast<void>(left.multiply(Matrix{4, 1})), std::invalid_argument);
EXPECT_THROW(static_cast<void>(left(2, 0)), std::out_of_range);
EXPECT_THROW(static_cast<void>(left(0, 3)), std::out_of_range);
EXPECT_THROW(static_cast<void>(const_left(2, 0)), std::out_of_range);
EXPECT_THROW(static_cast<void>(left.Multiply(Vector{2})),
std::invalid_argument);
EXPECT_THROW(static_cast<void>(left.Multiply(Matrix{4, 1})),
std::invalid_argument);
}
} // namespace
} // namespace fesa
} // namespace
} // namespace fesa
+95 -110
View File
@@ -1,6 +1,4 @@
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/matrix.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/math/sparse_matrix.h"
#include <gtest/gtest.h>
@@ -10,6 +8,9 @@
#include <utility>
#include <vector>
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/matrix.h"
namespace {
using fesa::CooContribution;
@@ -22,128 +23,112 @@ static_assert(
"SparseMatrix must own CSR storage independently of dense Matrix.");
TEST(SparseAssembly, ValidatesKnownCsrAndMultiply) {
const SparsePattern pattern{
{0U, 2U, 2U, 4U, 5U},
{0U, 2U, 1U, 3U, 3U}};
std::vector<CooContribution> contributions{
{2U, 3U, 4.0, 2U, 0U},
{0U, 2U, 2.0, 0U, 1U},
{3U, 3U, 5.0, 3U, 0U},
{0U, 0U, 1.0, 0U, 0U},
{2U, 1U, 3.0, 1U, 0U}};
const SparsePattern pattern{{0U, 2U, 2U, 4U, 5U}, {0U, 2U, 1U, 3U, 3U}};
std::vector<CooContribution> contributions{{2U, 3U, 4.0, 2U, 0U},
{0U, 2U, 2.0, 0U, 1U},
{3U, 3U, 5.0, 3U, 0U},
{0U, 0U, 1.0, 0U, 0U},
{2U, 1U, 3.0, 1U, 0U}};
auto result = SparseMatrix::fromCoo(
4U, 4U, std::move(contributions), pattern);
ASSERT_TRUE(result.hasValue());
const SparseMatrix& matrix = result.value();
auto result =
SparseMatrix::FromCoo(4U, 4U, std::move(contributions), pattern);
ASSERT_TRUE(result.HasValue());
const SparseMatrix& matrix = result.Value();
EXPECT_EQ(matrix.rows(), 4U);
EXPECT_EQ(matrix.columns(), 4U);
EXPECT_EQ(matrix.rowOffsets(), pattern.rowOffsets);
EXPECT_EQ(matrix.columnIndices(), pattern.columnIndices);
EXPECT_EQ(matrix.values(), (std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0}));
EXPECT_TRUE(matrix.validate().isOk());
EXPECT_EQ(matrix.Rows(), 4U);
EXPECT_EQ(matrix.Columns(), 4U);
EXPECT_EQ(matrix.RowOffsets(), pattern.rowOffsets);
EXPECT_EQ(matrix.ColumnIndices(), pattern.columnIndices);
EXPECT_EQ(matrix.Values(), (std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0}));
EXPECT_TRUE(matrix.Validate().IsOk());
Vector rhs{4U};
rhs[0U] = 1.0;
rhs[1U] = 2.0;
rhs[2U] = 3.0;
rhs[3U] = 4.0;
const Vector product = matrix.multiply(rhs);
ASSERT_EQ(product.size(), 4U);
EXPECT_DOUBLE_EQ(product[0U], 7.0);
EXPECT_DOUBLE_EQ(product[1U], 0.0);
EXPECT_DOUBLE_EQ(product[2U], 22.0);
EXPECT_DOUBLE_EQ(product[3U], 20.0);
EXPECT_THROW(static_cast<void>(matrix.multiply(Vector{3U})), std::invalid_argument);
Vector rhs{4U};
rhs[0U] = 1.0;
rhs[1U] = 2.0;
rhs[2U] = 3.0;
rhs[3U] = 4.0;
const Vector product = matrix.Multiply(rhs);
ASSERT_EQ(product.Size(), 4U);
EXPECT_DOUBLE_EQ(product[0U], 7.0);
EXPECT_DOUBLE_EQ(product[1U], 0.0);
EXPECT_DOUBLE_EQ(product[2U], 22.0);
EXPECT_DOUBLE_EQ(product[3U], 20.0);
EXPECT_THROW(static_cast<void>(matrix.Multiply(Vector{3U})),
std::invalid_argument);
}
TEST(SparseAssembly, ReducesDuplicatesInFixedTupleOrder) {
const SparsePattern pattern{{0U, 1U}, {0U}};
const std::vector<CooContribution> contributions{
{0U, 0U, 1.0, 2U, 0U},
{0U, 0U, -1.0e16, 1U, 0U},
{0U, 0U, 1.0e16, 0U, 0U}};
const SparsePattern pattern{{0U, 1U}, {0U}};
const std::vector<CooContribution> contributions{{0U, 0U, 1.0, 2U, 0U},
{0U, 0U, -1.0e16, 1U, 0U},
{0U, 0U, 1.0e16, 0U, 0U}};
auto first = SparseMatrix::fromCoo(1U, 1U, contributions, pattern);
ASSERT_TRUE(first.hasValue());
ASSERT_EQ(first.value().values().size(), 1U);
EXPECT_DOUBLE_EQ(first.value().values()[0U], 1.0);
auto first = SparseMatrix::FromCoo(1U, 1U, contributions, pattern);
ASSERT_TRUE(first.HasValue());
ASSERT_EQ(first.Value().Values().size(), 1U);
EXPECT_DOUBLE_EQ(first.Value().Values()[0U], 1.0);
auto reversedContributions = contributions;
std::reverse(reversedContributions.begin(), reversedContributions.end());
auto second = SparseMatrix::fromCoo(
1U, 1U, std::move(reversedContributions), pattern);
ASSERT_TRUE(second.hasValue());
EXPECT_EQ(second.value().rowOffsets(), first.value().rowOffsets());
EXPECT_EQ(second.value().columnIndices(), first.value().columnIndices());
EXPECT_EQ(second.value().values(), first.value().values());
auto reversed_contributions = contributions;
std::reverse(reversed_contributions.begin(), reversed_contributions.end());
auto second =
SparseMatrix::FromCoo(1U, 1U, std::move(reversed_contributions), pattern);
ASSERT_TRUE(second.HasValue());
EXPECT_EQ(second.Value().RowOffsets(), first.Value().RowOffsets());
EXPECT_EQ(second.Value().ColumnIndices(), first.Value().ColumnIndices());
EXPECT_EQ(second.Value().Values(), first.Value().Values());
}
TEST(SparseAssembly, RejectsInvalidIndexPatternAndShape) {
const SparsePattern oneEntry{{0U, 1U}, {0U}};
const auto expectFailure = [](
std::size_t rows,
std::size_t columns,
std::vector<CooContribution> contributions,
const SparsePattern& pattern) {
auto result = SparseMatrix::fromCoo(
rows, columns, std::move(contributions), pattern);
EXPECT_FALSE(result.hasValue());
if (!result.hasValue()) {
EXPECT_FALSE(result.status().isOk());
EXPECT_EQ(result.status().failureCategory(), fesa::FailureCategory::model);
EXPECT_FALSE(result.status().diagnostics().empty());
}
};
const SparsePattern one_entry{{0U, 1U}, {0U}};
const auto expect_failure = [](std::size_t rows, std::size_t columns,
std::vector<CooContribution> contributions,
const SparsePattern& pattern) {
auto result =
SparseMatrix::FromCoo(rows, columns, std::move(contributions), pattern);
EXPECT_FALSE(result.HasValue());
if (!result.HasValue()) {
EXPECT_FALSE(result.GetStatus().IsOk());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
EXPECT_FALSE(result.GetStatus().Diagnostics().empty());
}
};
expectFailure(2U, 2U, {}, {{0U, 0U}, {}});
expectFailure(1U, 1U, {}, {{1U, 1U}, {0U}});
expectFailure(2U, 2U, {}, {{0U, 1U, 0U}, {0U}});
expectFailure(1U, 2U, {}, {{0U, 2U}, {1U, 0U}});
expectFailure(1U, 1U, {}, {{0U, 2U}, {0U, 0U}});
expectFailure(1U, 1U, {}, {{0U, 1U}, {1U}});
expectFailure(1U, 1U, {{1U, 0U, 1.0, 0U, 0U}}, oneEntry);
expectFailure(1U, 1U, {{0U, 1U, 1.0, 0U, 0U}}, oneEntry);
expectFailure(1U, 2U, {{0U, 1U, 1.0, 0U, 0U}}, {{0U, 1U}, {0U}});
expectFailure(
1U,
1U,
{{0U, 0U, (std::numeric_limits<double>::infinity)(), 0U, 0U}},
oneEntry);
expectFailure(
1U,
1U,
{{0U, 0U, (std::numeric_limits<double>::quiet_NaN)(), 0U, 0U}},
oneEntry);
expectFailure(
1U,
1U,
{{0U, 0U, (std::numeric_limits<double>::max)(), 0U, 0U},
{0U, 0U, (std::numeric_limits<double>::max)(), 1U, 0U}},
oneEntry);
expect_failure(2U, 2U, {}, {{0U, 0U}, {}});
expect_failure(1U, 1U, {}, {{1U, 1U}, {0U}});
expect_failure(2U, 2U, {}, {{0U, 1U, 0U}, {0U}});
expect_failure(1U, 2U, {}, {{0U, 2U}, {1U, 0U}});
expect_failure(1U, 1U, {}, {{0U, 2U}, {0U, 0U}});
expect_failure(1U, 1U, {}, {{0U, 1U}, {1U}});
expect_failure(1U, 1U, {{1U, 0U, 1.0, 0U, 0U}}, one_entry);
expect_failure(1U, 1U, {{0U, 1U, 1.0, 0U, 0U}}, one_entry);
expect_failure(1U, 2U, {{0U, 1U, 1.0, 0U, 0U}}, {{0U, 1U}, {0U}});
expect_failure(1U, 1U,
{{0U, 0U, (std::numeric_limits<double>::infinity)(), 0U, 0U}},
one_entry);
expect_failure(1U, 1U,
{{0U, 0U, (std::numeric_limits<double>::quiet_NaN)(), 0U, 0U}},
one_entry);
expect_failure(1U, 1U,
{{0U, 0U, (std::numeric_limits<double>::max)(), 0U, 0U},
{0U, 0U, (std::numeric_limits<double>::max)(), 1U, 0U}},
one_entry);
}
TEST(SparseAssembly, PreservesExpectedStructuralZeros) {
const SparsePattern pattern{
{0U, 2U, 4U, 5U},
{0U, 2U, 1U, 2U, 0U}};
std::vector<CooContribution> contributions{
{0U, 0U, 2.0, 0U, 0U},
{1U, 1U, 4.0, 0U, 1U},
{1U, 1U, -4.0, 1U, 0U}};
const SparsePattern pattern{{0U, 2U, 4U, 5U}, {0U, 2U, 1U, 2U, 0U}};
std::vector<CooContribution> contributions{
{0U, 0U, 2.0, 0U, 0U}, {1U, 1U, 4.0, 0U, 1U}, {1U, 1U, -4.0, 1U, 0U}};
auto result = SparseMatrix::fromCoo(
3U, 3U, std::move(contributions), pattern);
ASSERT_TRUE(result.hasValue());
EXPECT_EQ(result.value().rowOffsets(), pattern.rowOffsets);
EXPECT_EQ(result.value().columnIndices(), pattern.columnIndices);
EXPECT_EQ(
result.value().values(),
(std::vector<double>{2.0, 0.0, 0.0, 0.0, 0.0}));
EXPECT_EQ(
std::count(result.value().values().begin(), result.value().values().end(), 0.0),
4);
auto result =
SparseMatrix::FromCoo(3U, 3U, std::move(contributions), pattern);
ASSERT_TRUE(result.HasValue());
EXPECT_EQ(result.Value().RowOffsets(), pattern.rowOffsets);
EXPECT_EQ(result.Value().ColumnIndices(), pattern.columnIndices);
EXPECT_EQ(result.Value().Values(),
(std::vector<double>{2.0, 0.0, 0.0, 0.0, 0.0}));
EXPECT_EQ(std::count(result.Value().Values().begin(),
result.Value().Values().end(), 0.0),
4);
}
} // namespace
} // namespace
+64 -63
View File
@@ -1,4 +1,4 @@
#include "fesa/math/vector.hpp"
#include "fesa/math/vector.h"
#include <gtest/gtest.h>
@@ -10,77 +10,78 @@ namespace fesa {
namespace {
TEST(DenseMath, VectorOwnsAndChecksContiguousStorage) {
Vector empty{0};
EXPECT_EQ(empty.size(), 0U);
EXPECT_DOUBLE_EQ(empty.norm(), 0.0);
EXPECT_NO_THROW(empty.scale(3.0));
EXPECT_NO_THROW(empty.axpy(-2.0, Vector{0}));
EXPECT_THROW(static_cast<void>(empty[0]), std::out_of_range);
Vector empty{0};
EXPECT_EQ(empty.Size(), 0U);
EXPECT_DOUBLE_EQ(empty.Norm(), 0.0);
EXPECT_NO_THROW(empty.Scale(3.0));
EXPECT_NO_THROW(empty.Axpy(-2.0, Vector{0}));
EXPECT_THROW(static_cast<void>(empty[0]), std::out_of_range);
Vector original{3};
original[0] = 1.0;
original[1] = -2.0;
original[2] = 3.0;
Vector original{3};
original[0] = 1.0;
original[1] = -2.0;
original[2] = 3.0;
EXPECT_EQ(original.data() + 1, &original[1]);
EXPECT_EQ(original.data() + 2, &original[2]);
const Vector& constOriginal = original;
EXPECT_EQ(constOriginal.data() + 2, &constOriginal[2]);
EXPECT_EQ(original.Data() + 1, &original[1]);
EXPECT_EQ(original.Data() + 2, &original[2]);
const Vector& const_original = original;
EXPECT_EQ(const_original.Data() + 2, &const_original[2]);
Vector copied{original};
EXPECT_NE(copied.data(), original.data());
copied[0] = 99.0;
EXPECT_DOUBLE_EQ(original[0], 1.0);
Vector copied{original};
EXPECT_NE(copied.Data(), original.Data());
copied[0] = 99.0;
EXPECT_DOUBLE_EQ(original[0], 1.0);
Vector copyAssigned{0};
copyAssigned = original;
EXPECT_NE(copyAssigned.data(), original.data());
copyAssigned[1] = 17.0;
EXPECT_DOUBLE_EQ(original[1], -2.0);
Vector copy_assigned{0};
copy_assigned = original;
EXPECT_NE(copy_assigned.Data(), original.Data());
copy_assigned[1] = 17.0;
EXPECT_DOUBLE_EQ(original[1], -2.0);
Vector moved{std::move(copied)};
EXPECT_EQ(copied.size(), 0U);
EXPECT_EQ(moved.size(), 3U);
EXPECT_DOUBLE_EQ(moved[0], 99.0);
EXPECT_NO_THROW(copied.scale(4.0));
Vector moved{std::move(copied)};
EXPECT_EQ(copied.Size(), 0U);
EXPECT_EQ(moved.Size(), 3U);
EXPECT_DOUBLE_EQ(moved[0], 99.0);
EXPECT_NO_THROW(copied.Scale(4.0));
Vector moveAssigned{1, -1.0};
moveAssigned = std::move(copyAssigned);
EXPECT_EQ(copyAssigned.size(), 0U);
EXPECT_EQ(moveAssigned.size(), 3U);
EXPECT_DOUBLE_EQ(moveAssigned[1], 17.0);
Vector move_assigned{1, -1.0};
move_assigned = std::move(copy_assigned);
EXPECT_EQ(copy_assigned.Size(), 0U);
EXPECT_EQ(move_assigned.Size(), 3U);
EXPECT_DOUBLE_EQ(move_assigned[1], 17.0);
Vector rhs{3};
rhs[0] = 4.0;
rhs[1] = 5.0;
rhs[2] = -6.0;
EXPECT_DOUBLE_EQ(original.dot(rhs), -24.0);
EXPECT_NEAR(original.norm(), std::sqrt(14.0), 1.0e-15);
Vector rhs{3};
rhs[0] = 4.0;
rhs[1] = 5.0;
rhs[2] = -6.0;
EXPECT_DOUBLE_EQ(original.Dot(rhs), -24.0);
EXPECT_NEAR(original.Norm(), std::sqrt(14.0), 1.0e-15);
Vector scaled{original};
scaled.scale(-0.5);
EXPECT_DOUBLE_EQ(scaled[0], -0.5);
EXPECT_DOUBLE_EQ(scaled[1], 1.0);
EXPECT_DOUBLE_EQ(scaled[2], -1.5);
Vector scaled{original};
scaled.Scale(-0.5);
EXPECT_DOUBLE_EQ(scaled[0], -0.5);
EXPECT_DOUBLE_EQ(scaled[1], 1.0);
EXPECT_DOUBLE_EQ(scaled[2], -1.5);
Vector accumulated{3};
accumulated[0] = 1.0;
accumulated[1] = 2.0;
accumulated[2] = 3.0;
Vector increment{3};
increment[0] = 4.0;
increment[1] = -1.0;
increment[2] = 0.5;
accumulated.axpy(2.0, increment);
EXPECT_DOUBLE_EQ(accumulated[0], 9.0);
EXPECT_DOUBLE_EQ(accumulated[1], 0.0);
EXPECT_DOUBLE_EQ(accumulated[2], 4.0);
Vector accumulated{3};
accumulated[0] = 1.0;
accumulated[1] = 2.0;
accumulated[2] = 3.0;
Vector increment{3};
increment[0] = 4.0;
increment[1] = -1.0;
increment[2] = 0.5;
accumulated.Axpy(2.0, increment);
EXPECT_DOUBLE_EQ(accumulated[0], 9.0);
EXPECT_DOUBLE_EQ(accumulated[1], 0.0);
EXPECT_DOUBLE_EQ(accumulated[2], 4.0);
EXPECT_THROW(static_cast<void>(original[3]), std::out_of_range);
EXPECT_THROW(static_cast<void>(constOriginal[3]), std::out_of_range);
EXPECT_THROW(static_cast<void>(original.dot(Vector{2})), std::invalid_argument);
EXPECT_THROW(original.axpy(1.0, Vector{2}), std::invalid_argument);
EXPECT_THROW(static_cast<void>(original[3]), std::out_of_range);
EXPECT_THROW(static_cast<void>(const_original[3]), std::out_of_range);
EXPECT_THROW(static_cast<void>(original.Dot(Vector{2})),
std::invalid_argument);
EXPECT_THROW(original.Axpy(1.0, Vector{2}), std::invalid_argument);
}
} // namespace
} // namespace fesa
} // namespace
} // namespace fesa
+27 -27
View File
@@ -59,7 +59,7 @@ fesa::ModelDefinition makeOwnedDefinition() {
1.0,
{"models/owned.inp", 55U}}};
definition.warnings = {{
fesa::Severity::warning,
fesa::Severity::kWarning,
"ignored-output-request",
{"models/owned.inp", 70U},
"*OUTPUT",
@@ -73,17 +73,17 @@ fesa::ModelDefinition makeOwnedDefinition() {
TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) {
auto definition = makeOwnedDefinition();
auto result = fesa::Domain::create(definition);
ASSERT_TRUE(result.hasValue());
ASSERT_TRUE(result.HasValue());
definition.sourcePath = "mutated.inp";
definition.sourceContentIdentity = "mutated";
definition.nodes[0].sourceId.sourceLabelText = "mutated";
definition.nodes[0].sourceId.source_label_text = "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::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()),
@@ -95,10 +95,10 @@ TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) {
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_EQ(domain.nodes()[0].sourceId.source_label, 20);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "0020");
EXPECT_DOUBLE_EQ(domain.nodes()[0].coordinates[0], 2.0);
EXPECT_EQ(domain.nodes()[1].sourceId.sourceLabel, 10);
EXPECT_EQ(domain.nodes()[1].sourceId.source_label, 10);
EXPECT_EQ(domain.nodes().data(), firstNodeAddress);
ASSERT_EQ(domain.elements().size(), 1U);
@@ -162,23 +162,23 @@ TEST(DomainModel, MultipleIdentityInstancesDoNotMerge) {
{"models/two-instances.inp", 60U}}};
auto result = fesa::Domain::create(std::move(definition));
ASSERT_TRUE(result.hasValue());
const fesa::Domain& domain = result.value();
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_EQ(domain.nodes()[0].sourceId.source_label, 1);
EXPECT_EQ(domain.nodes()[2].sourceId.source_label, 1);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "1");
EXPECT_EQ(domain.nodes()[2].sourceId.source_label_text, "1");
EXPECT_NE(
domain.nodes()[0].sourceId.instanceName,
domain.nodes()[2].sourceId.instanceName);
domain.nodes()[0].sourceId.instance_name,
domain.nodes()[2].sourceId.instance_name);
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].sourceId.source_label, 1);
EXPECT_EQ(domain.elements()[1].sourceId.source_label, 1);
EXPECT_EQ(domain.elements()[0].sourceId.instance_name, "Instance-A");
EXPECT_EQ(domain.elements()[1].sourceId.instance_name, "Instance-B");
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U);
EXPECT_EQ(domain.elements()[1].nodeIndices[0], 2U);
}
@@ -212,13 +212,13 @@ TEST(DomainModel, ShellOwnershipPreservesResolvedAssignmentsAndOptionalFrames) {
{0U, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, -1.0}}};
auto result = fesa::Domain::create(definition);
ASSERT_TRUE(result.hasValue());
ASSERT_TRUE(result.HasValue());
definition.shellSections[0].thickness = -1.0;
definition.shellElements[0].sourceId.sourceLabelText = "mutated";
definition.shellElements[0].sourceId.source_label_text = "mutated";
definition.shellNodeInitialFrames[0].director[2] = -1.0;
const fesa::Domain& domain = result.value();
const fesa::Domain& domain = result.Value();
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().shellElements()),
const std::vector<fesa::Mitc4ShellDefinition>&>);
@@ -230,9 +230,9 @@ TEST(DomainModel, ShellOwnershipPreservesResolvedAssignmentsAndOptionalFrames) {
const std::vector<fesa::ShellNodeInitialFrame>&>);
ASSERT_EQ(domain.shellElements().size(), 2U);
EXPECT_EQ(domain.shellElements()[0].sourceId.sourceLabel, 20);
EXPECT_EQ(domain.shellElements()[0].sourceId.sourceLabelText, "0020");
EXPECT_EQ(domain.shellElements()[1].sourceId.sourceLabel, 10);
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label, 20);
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label_text, "0020");
EXPECT_EQ(domain.shellElements()[1].sourceId.source_label, 10);
EXPECT_EQ(domain.shellElements()[0].sourceType, fesa::ShellSourceElementType::s4r);
EXPECT_EQ(domain.shellElements()[1].sourceType, fesa::ShellSourceElementType::s4);
EXPECT_EQ(domain.shellElements()[0].nodeIndices[3], 7U);
@@ -258,6 +258,6 @@ TEST(DomainModel, ShellOwnershipPreservesResolvedAssignmentsAndOptionalFrames) {
(std::array<double, 3>{0.0, 1.0, 0.0}));
auto noFramesResult = fesa::Domain::create(fesa::ModelDefinition{});
ASSERT_TRUE(noFramesResult.hasValue());
EXPECT_TRUE(noFramesResult.value().shellNodeInitialFrames().empty());
ASSERT_TRUE(noFramesResult.HasValue());
EXPECT_TRUE(noFramesResult.Value().shellNodeInitialFrames().empty());
}
+17 -17
View File
@@ -39,22 +39,22 @@ TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
EXPECT_EQ(
std::make_tuple(
firstIdentity.instanceName,
firstIdentity.sourceLabel,
firstIdentity.sourceLabelText),
firstIdentity.instance_name,
firstIdentity.source_label,
firstIdentity.source_label_text),
std::make_tuple(
equalIdentity.instanceName,
equalIdentity.sourceLabel,
equalIdentity.sourceLabelText));
equalIdentity.instance_name,
equalIdentity.source_label,
equalIdentity.source_label_text));
EXPECT_LT(
std::make_tuple(
firstIdentity.instanceName,
firstIdentity.sourceLabel,
firstIdentity.sourceLabelText),
firstIdentity.instance_name,
firstIdentity.source_label,
firstIdentity.source_label_text),
std::make_tuple(
laterIdentity.instanceName,
laterIdentity.sourceLabel,
laterIdentity.sourceLabelText));
laterIdentity.instance_name,
laterIdentity.source_label,
laterIdentity.source_label_text));
const fesa::Node node{firstIdentity, {1.0, 2.0, 3.0}, nodeLocation};
const fesa::LinearElasticMaterial material{
@@ -116,7 +116,7 @@ TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
definition.instances = {instance};
definition.steps = {step};
definition.warnings = {{
fesa::Severity::warning,
fesa::Severity::kWarning,
"ignored-output-request",
{"models/beam.inp", 75U},
"*OUTPUT",
@@ -124,7 +124,7 @@ TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
"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].sourceId.source_label_text, "0007");
EXPECT_EQ(definition.nodes[0].location.line, 11U);
ASSERT_EQ(definition.instances.size(), 1U);
EXPECT_EQ(definition.instances[0].partName, "BeamPart");
@@ -167,9 +167,9 @@ TEST(DomainModel, Mitc4ShellRecordsPreserveSourceAndInternalIdentity) {
fesa::EntityIndex{7},
{"models/shell.inp", 21U}};
EXPECT_EQ(s4Element.sourceId.instanceName, "Shell-Instance");
EXPECT_EQ(s4Element.sourceId.sourceLabel, 41);
EXPECT_EQ(s4Element.sourceId.sourceLabelText, "0041");
EXPECT_EQ(s4Element.sourceId.instance_name, "Shell-Instance");
EXPECT_EQ(s4Element.sourceId.source_label, 41);
EXPECT_EQ(s4Element.sourceId.source_label_text, "0041");
EXPECT_EQ(s4Element.sourceType, fesa::ShellSourceElementType::s4);
EXPECT_EQ(s4rElement.sourceType, fesa::ShellSourceElementType::s4r);
EXPECT_NE(s4Element.sourceType, s4rElement.sourceType);
+19 -19
View File
@@ -89,10 +89,10 @@ const fesa::ShellNodeInitialFrame& frameFor(
void expectFailureCode(
const fesa::Result<fesa::ShellGeometry>& result,
const std::string& code) {
ASSERT_FALSE(result.hasValue());
EXPECT_EQ(result.status().failureCategory(), fesa::FailureCategory::model);
ASSERT_EQ(result.status().diagnostics().size(), 1U);
EXPECT_EQ(result.status().diagnostics()[0].code, code);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(result.GetStatus().Diagnostics()[0].code, code);
}
} // namespace
@@ -107,12 +107,12 @@ TEST(Mitc4Geometry, BuildsDeterministicFramesForPlanarRotatedAndWarpedElements)
auto planar = fesa::preprocessShellGeometry(
planarNodes, {element(10U, {0U, 1U, 2U, 3U})}, sections());
ASSERT_TRUE(planar.hasValue());
ASSERT_EQ(planar.value().elementData.size(), 1U);
expectVectorNear(planar.value().elementData[0].normalCandidate, {0.0, 0.0, 1.0});
EXPECT_NEAR(planar.value().elementData[0].surfaceAreaWeight, 1.0, 1.0e-12);
ASSERT_EQ(planar.value().nodalFrames.size(), 4U);
for (const auto& frame : planar.value().nodalFrames) {
ASSERT_TRUE(planar.HasValue());
ASSERT_EQ(planar.Value().elementData.size(), 1U);
expectVectorNear(planar.Value().elementData[0].normalCandidate, {0.0, 0.0, 1.0});
EXPECT_NEAR(planar.Value().elementData[0].surfaceAreaWeight, 1.0, 1.0e-12);
ASSERT_EQ(planar.Value().nodalFrames.size(), 4U);
for (const auto& frame : planar.Value().nodalFrames) {
expectVectorNear(frame.director, {0.0, 0.0, 1.0});
expectVectorNear(frame.tangentA, {1.0, 0.0, 0.0});
expectVectorNear(frame.tangentB, {0.0, 1.0, 0.0});
@@ -127,8 +127,8 @@ TEST(Mitc4Geometry, BuildsDeterministicFramesForPlanarRotatedAndWarpedElements)
auto rotated = fesa::preprocessShellGeometry(
rotatedNodes, {element(11U, {0U, 1U, 2U, 3U})}, sections());
ASSERT_TRUE(rotated.hasValue());
const auto& rotatedFrame = frameFor(rotated.value(), 0U);
ASSERT_TRUE(rotated.HasValue());
const auto& rotatedFrame = frameFor(rotated.Value(), 0U);
expectVectorNear(rotatedFrame.director, {1.0, 0.0, 0.0});
expectVectorNear(rotatedFrame.tangentA, {0.0, 1.0, 0.0});
expectVectorNear(rotatedFrame.tangentB, {0.0, 0.0, 1.0});
@@ -142,9 +142,9 @@ TEST(Mitc4Geometry, BuildsDeterministicFramesForPlanarRotatedAndWarpedElements)
auto warped = fesa::preprocessShellGeometry(
warpedNodes, {element(12U, {0U, 1U, 2U, 3U})}, sections());
ASSERT_TRUE(warped.hasValue());
EXPECT_GT(warped.value().elementData[0].surfaceAreaWeight, 2.0);
for (const auto& frame : warped.value().nodalFrames) {
ASSERT_TRUE(warped.HasValue());
EXPECT_GT(warped.Value().elementData[0].surfaceAreaWeight, 2.0);
for (const auto& frame : warped.Value().nodalFrames) {
expectRightHandedFrame(frame);
}
}
@@ -166,13 +166,13 @@ TEST(Mitc4Geometry, AreaWeightsSharedDirectorsInStableSourceIdentityOrder) {
auto second = fesa::preprocessShellGeometry(
nodes, {flat, tilted}, sections());
ASSERT_TRUE(first.hasValue());
ASSERT_TRUE(second.hasValue());
ASSERT_TRUE(first.HasValue());
ASSERT_TRUE(second.HasValue());
const Vector3 expectedSharedDirector{
-1.0 / std::sqrt(5.0), 0.0, 2.0 / std::sqrt(5.0)};
for (const auto sharedNode : {1U, 2U}) {
const auto& firstFrame = frameFor(first.value(), sharedNode);
const auto& secondFrame = frameFor(second.value(), sharedNode);
const auto& firstFrame = frameFor(first.Value(), sharedNode);
const auto& secondFrame = frameFor(second.Value(), sharedNode);
expectVectorNear(firstFrame.director, expectedSharedDirector);
expectVectorNear(firstFrame.director, secondFrame.director, 0.0);
expectVectorNear(firstFrame.tangentA, {0.0, 1.0, 0.0});
+15 -15
View File
@@ -20,12 +20,12 @@ fesa::DofManager makeEmptyDofs() {
{definition.sourcePath, 10U}}};
auto domain = fesa::Domain::create(std::move(definition));
EXPECT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
EXPECT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
EXPECT_TRUE(dofs.hasValue());
return std::move(dofs.value());
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
EXPECT_TRUE(dofs.HasValue());
return std::move(dofs.Value());
}
fesa::ShellResultRow makeShellRow(
@@ -141,15 +141,15 @@ void expectShellCandidateRejectedWithoutMutation(
const fesa::ShellStateCandidate& candidate,
const fesa::ShellStateCandidate& committed) {
const auto status = state.commitShellResults(expectedElements, candidate);
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::model);
EXPECT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
expectShellStateEquals(
state,
committed.rows,
committed.physicalStrainEnergy,
committed.equilibrium,
committed.verificationMetrics);
ASSERT_TRUE(state.commitShellResults(expectedElements, committed).isOk());
ASSERT_TRUE(state.commitShellResults(expectedElements, committed).IsOk());
}
} // namespace
@@ -197,9 +197,9 @@ TEST(AnalysisState, PreservesStepFrameAndStableRowOrder) {
EXPECT_EQ(constState.endpointResults().data(), endpointStorage);
EXPECT_EQ(constState.endpointResults()[0].element, 2U);
EXPECT_EQ(constState.endpointResults()[0].endpoint, -1);
EXPECT_EQ(constState.endpointResults()[0].node.instanceName, "Beam-1");
EXPECT_EQ(constState.endpointResults()[0].node.sourceLabel, 10);
EXPECT_EQ(constState.endpointResults()[0].node.sourceLabelText, "010");
EXPECT_EQ(constState.endpointResults()[0].node.instance_name, "Beam-1");
EXPECT_EQ(constState.endpointResults()[0].node.source_label, 10);
EXPECT_EQ(constState.endpointResults()[0].node.source_label_text, "010");
EXPECT_EQ(
constState.endpointResults()[0].endAction,
(std::array<double, 6>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}));
@@ -237,7 +237,7 @@ TEST(AnalysisState, OwnsExactShellRowsInStableElementAndLocationOrder) {
const auto status = state.commitShellResults(expectedElements, candidate);
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(status.IsOk());
const fesa::AnalysisState& constState = state;
ASSERT_EQ(constState.shellResults().size(), 8U);
EXPECT_EQ(constState.shellResults()[0].element, 3U);
@@ -283,7 +283,7 @@ TEST(AnalysisState, CommitsFiniteShellGlobalEvidence) {
const auto status = state.commitShellResults(expectedElements, candidate);
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(status.IsOk());
EXPECT_DOUBLE_EQ(state.physicalStrainEnergy(), 35.5);
EXPECT_EQ(
state.equilibrium(),
@@ -299,7 +299,7 @@ TEST(AnalysisState, InvalidShellCandidatesLeavePriorStateUnchanged) {
auto state = fesa::AnalysisState::create(dofs, {"Step-1", 0U});
const std::vector<fesa::EntityIndex> expectedElements{5U};
const auto committed = makeShellCandidate(expectedElements);
ASSERT_TRUE(state.commitShellResults(expectedElements, committed).isOk());
ASSERT_TRUE(state.commitShellResults(expectedElements, committed).IsOk());
auto invalidLocation = makeShellCandidate(expectedElements);
invalidLocation.rows[0].location = fesa::ShellMidsurfaceLocation::gp2;
+73 -73
View File
@@ -117,34 +117,34 @@ RecoveryFixture makeFixture(
reverseSecond,
sectionJump,
nonzeroPrescription));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{"Recovery fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Recovery fixture AnalysisModel construction failed."};
}
auto model = std::make_unique<fesa::AnalysisModel>(
std::move(modelResult.value()));
std::move(modelResult.Value()));
auto dofsResult = fesa::DofManager::create(*model);
if (!dofsResult.hasValue()) {
if (!dofsResult.HasValue()) {
throw std::runtime_error{"Recovery fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofsResult.value()));
std::move(dofsResult.Value()));
fesa::SerialParallelFor serial;
auto stiffnessResult = fesa::SparseAssembler::assembleStiffness(
*model, *dofs, serial);
if (!stiffnessResult.hasValue()) {
if (!stiffnessResult.HasValue()) {
throw std::runtime_error{"Recovery fixture stiffness assembly failed."};
}
auto stiffness = std::make_unique<fesa::SparseMatrix>(
std::move(stiffnessResult.value()));
std::move(stiffnessResult.Value()));
return {
std::move(domain),
std::move(model),
@@ -226,38 +226,38 @@ fesa::ModelDefinition makeShellDefinition(
ShellRecoveryFixture makeShellFixture(fesa::ModelDefinition definition) {
auto domainResult = fesa::Domain::create(std::move(definition));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{
"Shell recovery fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{
"Shell recovery fixture AnalysisModel construction failed."};
}
auto model = std::make_unique<fesa::AnalysisModel>(
std::move(modelResult.value()));
std::move(modelResult.Value()));
auto dofsResult = fesa::DofManager::create(*model);
if (!dofsResult.hasValue()) {
if (!dofsResult.HasValue()) {
throw std::runtime_error{
"Shell recovery fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofsResult.value()));
std::move(dofsResult.Value()));
fesa::SerialParallelFor serial;
auto stiffnessResult = fesa::SparseAssembler::assembleStiffness(
*model, *dofs, serial);
if (!stiffnessResult.hasValue()) {
if (!stiffnessResult.HasValue()) {
throw std::runtime_error{
"Shell recovery fixture stiffness assembly failed."};
}
auto stiffness = std::make_unique<fesa::SparseMatrix>(
std::move(stiffnessResult.value()));
std::move(stiffnessResult.Value()));
return {
std::move(domain),
std::move(model),
@@ -290,7 +290,7 @@ fesa::AnalysisState makeShellPhysicalState(
generalized[3U] * x + 0.5 * generalized[5U] * y;
}
state.externalForce() =
fixture.stiffness->multiply(state.displacement());
fixture.stiffness->Multiply(state.displacement());
return state;
}
@@ -299,7 +299,7 @@ fesa::AnalysisState makeAxialEquilibriumState(const RecoveryFixture& fixture) {
*fixture.dofs, {"Step-1", 0U});
state.displacement()[0U] = 0.1;
state.displacement()[6U] = 0.3;
const fesa::Vector internal = fixture.stiffness->multiply(state.displacement());
const fesa::Vector internal = fixture.stiffness->Multiply(state.displacement());
for (const std::size_t fullDof : fixture.dofs->freeDofs()) {
state.externalForce()[fullDof] = internal[fullDof];
}
@@ -321,15 +321,15 @@ fesa::AnalysisState makePatchState(
state.displacement()[9U] = twist * kLength;
state.displacement()[10U] = kappaY * kLength;
state.displacement()[11U] = kappaZ * kLength;
state.externalForce() = fixture.stiffness->multiply(state.displacement());
state.externalForce() = fixture.stiffness->Multiply(state.displacement());
return state;
}
void expectStatusCode(const fesa::Status& status, const std::string& code) {
ASSERT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::model);
ASSERT_EQ(status.diagnostics().size(), 1U);
EXPECT_EQ(status.diagnostics()[0U].code, code);
ASSERT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(status.Diagnostics().size(), 1U);
EXPECT_EQ(status.Diagnostics()[0U].code, code);
}
void expectScaledNear(
@@ -371,12 +371,12 @@ TEST(ResultRecovery, ComputesResidualReactionForNonzeroPrescription) {
staleShellEvidence.physicalStrainEnergy = 123.0;
staleShellEvidence.equilibrium = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
staleShellEvidence.verificationMetrics = {1.0e-11, 2.0e-11, 3.0e-11};
ASSERT_TRUE(state.commitShellResults({}, staleShellEvidence).isOk());
ASSERT_TRUE(state.commitShellResults({}, staleShellEvidence).IsOk());
const fesa::Status status = fesa::ResultRecovery::recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state);
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(status.IsOk());
EXPECT_DOUBLE_EQ(state.internalForce()[0U], -20.0);
EXPECT_DOUBLE_EQ(state.internalForce()[6U], 20.0);
EXPECT_DOUBLE_EQ(state.residual()[0U], -20.0);
@@ -422,7 +422,7 @@ TEST(ResultRecovery, EnforcesNormalizedFreeResidual) {
*fixture.dofs,
*fixture.stiffness,
thresholdPass);
ASSERT_TRUE(thresholdStatus.isOk());
ASSERT_TRUE(thresholdStatus.IsOk());
EXPECT_NE(thresholdPass.residual()[6U], 0.0);
EXPECT_DOUBLE_EQ(
thresholdPass.reaction()[6U], thresholdPass.residual()[6U]);
@@ -435,7 +435,7 @@ TEST(ResultRecovery, EnforcesNormalizedFreeResidual) {
*zeroFixture.dofs,
*zeroFixture.stiffness,
zeroEquilibrium)
.isOk());
.IsOk());
auto wrongPrescription = makeAxialEquilibriumState(fixture);
wrongPrescription.displacement()[0U] = 0.0;
@@ -469,7 +469,7 @@ TEST(ResultRecovery, KeepsEndActionSectionAndGaussResultsDistinct) {
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state)
.isOk());
.IsOk());
ASSERT_EQ(state.endpointResults().size(), 2U);
ASSERT_EQ(state.gaussResults().size(), 2U);
EXPECT_EQ(state.endpointResults()[0U].endpoint, 0);
@@ -497,7 +497,7 @@ TEST(ResultRecovery, MatchesAxialTorsionAndTwoPlaneEndSigns) {
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state)
.isOk());
.IsOk());
const double shearModulus =
kYoungsModulus / (2.0 * (1.0 + kPoissonRatio));
const std::array<double, 4> expected = {
@@ -529,7 +529,7 @@ TEST(ResultRecovery, OrdersStressPointsAndDefaultCentroid) {
auto state = makePatchState(fixture, 0.01, 0.0, 0.02, -0.03);
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state)
.isOk());
.IsOk());
ASSERT_EQ(state.stressResults().size(), 4U);
for (std::size_t gauss = 0U; gauss < 2U; ++gauss) {
for (std::size_t point = 0U; point < sectionPoints.size(); ++point) {
@@ -554,7 +554,7 @@ TEST(ResultRecovery, OrdersStressPointsAndDefaultCentroid) {
*defaultFixture.dofs,
*defaultFixture.stiffness,
defaultState)
.isOk());
.IsOk());
ASSERT_EQ(defaultState.stressResults().size(), 2U);
for (const auto& row : defaultState.stressResults()) {
EXPECT_EQ(row.sectionPoint, 0U);
@@ -573,17 +573,17 @@ TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
auto normalized =
fesa::ResultRecovery::normalizeSectionResultantsToNodeStations(
*fixture.model, rows, tolerances);
ASSERT_TRUE(normalized.hasValue());
ASSERT_EQ(normalized.value().size(), 3U);
EXPECT_EQ(normalized.value()[1U].representativeElement, 0U);
EXPECT_DOUBLE_EQ(normalized.value()[1U].sectionResultant[0U], 5.0);
ASSERT_TRUE(normalized.HasValue());
ASSERT_EQ(normalized.Value().size(), 3U);
EXPECT_EQ(normalized.Value()[1U].representativeElement, 0U);
EXPECT_DOUBLE_EQ(normalized.Value()[1U].sectionResultant[0U], 5.0);
rows[2U].sectionResultant[0U] = 5.0 + 2.0e-6;
auto mismatch =
fesa::ResultRecovery::normalizeSectionResultantsToNodeStations(
*fixture.model, rows, tolerances);
ASSERT_FALSE(mismatch.hasValue());
expectStatusCode(mismatch.status(), "node-station-tolerance-failure");
ASSERT_FALSE(mismatch.HasValue());
expectStatusCode(mismatch.GetStatus(), "node-station-tolerance-failure");
rows = makeStationRows(fixture);
rows[2U].sectionResultant[1U] =
@@ -591,17 +591,17 @@ TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
auto nonfinite =
fesa::ResultRecovery::normalizeSectionResultantsToNodeStations(
*fixture.model, rows, tolerances);
ASSERT_FALSE(nonfinite.hasValue());
expectStatusCode(nonfinite.status(), "nonfinite-node-station-value");
ASSERT_FALSE(nonfinite.HasValue());
expectStatusCode(nonfinite.GetStatus(), "nonfinite-node-station-value");
auto invalidTolerance =
fesa::ResultRecovery::normalizeSectionResultantsToNodeStations(
*fixture.model,
makeStationRows(fixture),
{1.0e-6, -1.0, 1.0e-6, 1.0e-6});
ASSERT_FALSE(invalidTolerance.hasValue());
ASSERT_FALSE(invalidTolerance.HasValue());
expectStatusCode(
invalidTolerance.status(), "invalid-node-station-tolerance");
invalidTolerance.GetStatus(), "invalid-node-station-tolerance");
const std::filesystem::path source{"models/result-recovery.inp"};
const auto loadedFixture = makeFixture(
@@ -611,8 +611,8 @@ TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
*loadedFixture.model,
makeStationRows(loadedFixture),
tolerances);
ASSERT_FALSE(loaded.hasValue());
expectStatusCode(loaded.status(), "ineligible-node-station");
ASSERT_FALSE(loaded.HasValue());
expectStatusCode(loaded.GetStatus(), "ineligible-node-station");
const auto reversedFixture = makeFixture(true, {}, {}, true);
auto reversed =
@@ -620,8 +620,8 @@ TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
*reversedFixture.model,
makeStationRows(reversedFixture),
tolerances);
ASSERT_FALSE(reversed.hasValue());
expectStatusCode(reversed.status(), "ineligible-node-station");
ASSERT_FALSE(reversed.HasValue());
expectStatusCode(reversed.GetStatus(), "ineligible-node-station");
const auto jumpFixture = makeFixture(true, {}, {}, false, true);
auto jumped =
@@ -629,8 +629,8 @@ TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
*jumpFixture.model,
makeStationRows(jumpFixture),
tolerances);
ASSERT_FALSE(jumped.hasValue());
expectStatusCode(jumped.status(), "ineligible-node-station");
ASSERT_FALSE(jumped.HasValue());
expectStatusCode(jumped.GetStatus(), "ineligible-node-station");
}
// MITC4-REC-001
@@ -647,12 +647,12 @@ TEST(ResultRecovery, RecoversShellRowsInStableElementAndGpOrder) {
state.displacement()[node * 6U + 1U] = -0.05 * y + 0.1 * x;
}
state.externalForce() =
fixture.stiffness->multiply(state.displacement());
fixture.stiffness->Multiply(state.displacement());
const auto status = fesa::ResultRecovery::recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state);
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(state.shellResults().size(), 8U);
const double gauss = 1.0 / std::sqrt(3.0);
const std::array<fesa::ShellMidsurfaceLocation, 4> locations{
@@ -706,7 +706,7 @@ TEST(ResultRecovery, RecoversDirectBottomMiddleTopShellStress) {
*fixture.dofs,
*fixture.stiffness,
state)
.isOk());
.IsOk());
constexpr std::array<fesa::ShellSectionPosition, 3> positions{
fesa::ShellSectionPosition::bottom,
fesa::ShellSectionPosition::middle,
@@ -744,16 +744,16 @@ TEST(ResultRecovery, SumsOnlyPhysicalShellEnergyInSourceOrder) {
state.displacement()[node * 6U + 5U] = drill[node];
}
state.externalForce() =
fixture.stiffness->multiply(state.displacement());
const double stabilizedEnergy = 0.5 * state.displacement().dot(
fixture.stiffness->multiply(state.displacement()));
fixture.stiffness->Multiply(state.displacement());
const double stabilizedEnergy = 0.5 * state.displacement().Dot(
fixture.stiffness->Multiply(state.displacement()));
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model,
*fixture.dofs,
*fixture.stiffness,
state)
.isOk());
.IsOk());
EXPECT_NEAR(state.physicalStrainEnergy(), 72.16, 1.0e-12);
EXPECT_GT(stabilizedEnergy, state.physicalStrainEnergy());
}
@@ -772,15 +772,15 @@ TEST(ResultRecovery, KeepsFullResidualAndComputesGlobalShellEquilibrium) {
*fixture.dofs, {"Step-1", 0U});
auto fullLoad = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_TRUE(fullLoad.hasValue());
state.externalForce() = std::move(fullLoad.value());
ASSERT_TRUE(fullLoad.HasValue());
state.externalForce() = std::move(fullLoad.Value());
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model,
*fixture.dofs,
*fixture.stiffness,
state)
.isOk());
.IsOk());
ASSERT_EQ(state.shellResults().size(), 4U);
for (std::size_t fullDof = 0U;
fullDof < fixture.dofs->fullDofCount();
@@ -805,7 +805,7 @@ TEST(ResultRecovery, KeepsFullResidualAndComputesGlobalShellEquilibrium) {
*freeFixture.dofs,
*freeFixture.stiffness,
perturbed)
.isOk());
.IsOk());
for (const double metric : perturbed.verificationMetrics()) {
EXPECT_GT(metric, 0.0);
EXPECT_LE(metric, 1.0e-10);
@@ -838,13 +838,13 @@ TEST(ResultRecovery, UsesGlobalOriginForShellMomentBalance) {
*centeredFixture.dofs,
*centeredFixture.stiffness,
centered)
.isOk());
.IsOk());
ASSERT_TRUE(fesa::ResultRecovery::recover(
*translatedFixture.model,
*translatedFixture.dofs,
*translatedFixture.stiffness,
translated)
.isOk());
.IsOk());
std::array<double, 3> centeredForce{};
for (std::size_t component = 0U; component < 3U; ++component) {
centeredForce[component] = centered.equilibrium()[component];
@@ -877,11 +877,11 @@ TEST(ResultRecovery, UsesScaleAwareShellMetricsAndRejectsExcess) {
auto large = makeShellPhysicalState(fixture);
constexpr double subunitScale = 1.0e-6;
constexpr double largeScale = 1.0e6;
subunit.displacement().scale(subunitScale);
subunit.externalForce().scale(subunitScale);
subunit.displacement().Scale(subunitScale);
subunit.externalForce().Scale(subunitScale);
subunit.externalForce()[0U] += 1.0e-9 * subunitScale;
large.displacement().scale(largeScale);
large.externalForce().scale(largeScale);
large.displacement().Scale(largeScale);
large.externalForce().Scale(largeScale);
large.externalForce()[0U] += 1.0e-9 * largeScale;
ASSERT_TRUE(fesa::ResultRecovery::recover(
@@ -889,13 +889,13 @@ TEST(ResultRecovery, UsesScaleAwareShellMetricsAndRejectsExcess) {
*fixture.dofs,
*fixture.stiffness,
subunit)
.isOk());
.IsOk());
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model,
*fixture.dofs,
*fixture.stiffness,
large)
.isOk());
.IsOk());
for (std::size_t metric = 0U; metric < 3U; ++metric) {
EXPECT_GT(subunit.verificationMetrics()[metric], 0.0);
EXPECT_GT(large.verificationMetrics()[metric], 0.0);
@@ -918,17 +918,17 @@ TEST(ResultRecovery, UsesScaleAwareShellMetricsAndRejectsExcess) {
}
const std::vector<fesa::CooContribution> unbalancedEntry{
{0U, 0U, 1.0, 0U, 0U}};
auto unbalancedStiffness = fesa::SparseMatrix::fromCoo(
auto unbalancedStiffness = fesa::SparseMatrix::FromCoo(
constrainedFixture.dofs->fullDofCount(),
constrainedFixture.dofs->fullDofCount(),
unbalancedEntry,
constrainedFixture.dofs->sparsePattern());
ASSERT_TRUE(unbalancedStiffness.hasValue());
ASSERT_TRUE(unbalancedStiffness.HasValue());
expectStatusCode(
fesa::ResultRecovery::recover(
*constrainedFixture.model,
*constrainedFixture.dofs,
unbalancedStiffness.value(),
unbalancedStiffness.Value(),
rejected),
"global-equilibrium-tolerance-failure");
}
@@ -942,7 +942,7 @@ TEST(ResultRecovery, InvalidLaterShellLeavesEntirePriorStateUnchanged) {
*validFixture.dofs,
*validFixture.stiffness,
state)
.isOk());
.IsOk());
ASSERT_EQ(state.shellResults().size(), 8U);
const auto priorFirstRow = state.shellResults().front();
const double priorEnergy = state.physicalStrainEnergy();
@@ -957,17 +957,17 @@ TEST(ResultRecovery, InvalidLaterShellLeavesEntirePriorStateUnchanged) {
state.displacement()[5U * 6U] =
(std::numeric_limits<double>::max)();
state.externalForce() = fesa::Vector{validFixture.dofs->fullDofCount()};
auto zeroStiffness = fesa::SparseMatrix::fromCoo(
auto zeroStiffness = fesa::SparseMatrix::FromCoo(
validFixture.dofs->fullDofCount(),
validFixture.dofs->fullDofCount(),
{},
validFixture.dofs->sparsePattern());
ASSERT_TRUE(zeroStiffness.hasValue());
ASSERT_TRUE(zeroStiffness.HasValue());
const auto status = fesa::ResultRecovery::recover(
*validFixture.model,
*validFixture.dofs,
zeroStiffness.value(),
zeroStiffness.Value(),
state);
expectStatusCode(status, "invalid-shell-recovery");
+2 -2
View File
@@ -1,8 +1,8 @@
#include "fesa/results/results_writer.hpp"
#include "fesa/analysis/analysis_state.hpp"
#include "fesa/core/diagnostic.hpp"
#include "fesa/core/status.hpp"
#include "fesa/core/diagnostic.h"
#include "fesa/core/status.h"
#include "fesa/model/domain.hpp"
#include <filesystem>
+98 -117
View File
@@ -1,8 +1,4 @@
#include "fesa/solvers/linear/linear_solver.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/solvers/linear/linear_solver.h"
#include <gtest/gtest.h>
@@ -10,137 +6,122 @@
#include <utility>
#include <vector>
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
namespace {
fesa::SparseMatrix makeDenseCsr(
const std::size_t rows,
const std::size_t columns,
const std::vector<double>& values) {
EXPECT_EQ(values.size(), rows * columns);
fesa::SparseMatrix MakeDenseCsr(const std::size_t rows,
const std::size_t columns,
const std::vector<double>& values) {
EXPECT_EQ(values.size(), rows * columns);
fesa::SparsePattern pattern;
std::vector<fesa::CooContribution> contributions;
pattern.rowOffsets.reserve(rows + 1U);
pattern.rowOffsets.push_back(0U);
for (std::size_t row = 0U; row < rows; ++row) {
for (std::size_t column = 0U; column < columns; ++column) {
pattern.columnIndices.push_back(column);
contributions.push_back({
row,
column,
values[row * columns + column],
row,
column});
}
pattern.rowOffsets.push_back(pattern.columnIndices.size());
fesa::SparsePattern pattern;
std::vector<fesa::CooContribution> contributions;
pattern.rowOffsets.reserve(rows + 1U);
pattern.rowOffsets.push_back(0U);
for (std::size_t row = 0U; row < rows; ++row) {
for (std::size_t column = 0U; column < columns; ++column) {
pattern.columnIndices.push_back(column);
contributions.push_back(
{row, column, values[row * columns + column], row, column});
}
pattern.rowOffsets.push_back(pattern.columnIndices.size());
}
auto matrix = fesa::SparseMatrix::fromCoo(
rows, columns, std::move(contributions), pattern);
EXPECT_TRUE(matrix.hasValue());
return std::move(matrix.value());
auto matrix = fesa::SparseMatrix::FromCoo(rows, columns,
std::move(contributions), pattern);
EXPECT_TRUE(matrix.HasValue());
return std::move(matrix.Value());
}
void expectSolverFailure(const fesa::Status& status) {
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::solver);
ASSERT_FALSE(status.diagnostics().empty());
EXPECT_EQ(status.diagnostics().front().severity, fesa::Severity::error);
void ExpectSolverFailure(const fesa::Status& status) {
EXPECT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kSolver);
ASSERT_FALSE(status.Diagnostics().empty());
EXPECT_EQ(status.Diagnostics().front().severity, fesa::Severity::kError);
}
} // namespace
} // namespace
TEST(MklPardisoSolver, RejectsInvalidCsrStateAndDimensions) {
static_assert(std::is_base_of_v<fesa::LinearSolver, fesa::MklPardisoSolver>);
static_assert(std::has_virtual_destructor_v<fesa::LinearSolver>);
static_assert(std::is_base_of_v<fesa::LinearSolver, fesa::MklPardisoSolver>);
static_assert(std::has_virtual_destructor_v<fesa::LinearSolver>);
fesa::MklPardisoSolver solver;
fesa::Vector untouched{2U};
untouched[0U] = 17.0;
untouched[1U] = -4.0;
const auto beforeFactorize =
solver.solve(fesa::Vector{2U, 1.0}, untouched);
expectSolverFailure(beforeFactorize);
EXPECT_EQ(
beforeFactorize.diagnostics().front().code,
"solver-not-factorized");
EXPECT_DOUBLE_EQ(untouched[0U], 17.0);
EXPECT_DOUBLE_EQ(untouched[1U], -4.0);
fesa::MklPardisoSolver solver;
fesa::Vector untouched{2U};
untouched[0U] = 17.0;
untouched[1U] = -4.0;
const auto before_factorize = solver.Solve(fesa::Vector{2U, 1.0}, untouched);
ExpectSolverFailure(before_factorize);
EXPECT_EQ(before_factorize.Diagnostics().front().code,
"solver-not-factorized");
EXPECT_DOUBLE_EQ(untouched[0U], 17.0);
EXPECT_DOUBLE_EQ(untouched[1U], -4.0);
// A fully constrained model has a valid 0x0 Kff. It still observes the
// factorize-then-solve lifecycle without invoking a numerical backend.
const auto empty = makeDenseCsr(0U, 0U, {});
ASSERT_TRUE(solver.factorize(empty).isOk());
fesa::Vector emptySolution{0U};
EXPECT_TRUE(solver.solve(fesa::Vector{0U}, emptySolution).isOk());
EXPECT_EQ(emptySolution.size(), 0U);
// A fully constrained model has a valid 0x0 Kff. It still observes the
// factorize-then-solve lifecycle without invoking a numerical backend.
const auto empty = MakeDenseCsr(0U, 0U, {});
ASSERT_TRUE(solver.Factorize(empty).IsOk());
fesa::Vector empty_solution{0U};
EXPECT_TRUE(solver.Solve(fesa::Vector{0U}, empty_solution).IsOk());
EXPECT_EQ(empty_solution.Size(), 0U);
// Refactorization from the trivial state must establish ordinary PARDISO
// state rather than retaining a zero-equation shortcut.
const auto spd = makeDenseCsr(2U, 2U, {4.0, 1.0, 1.0, 3.0});
ASSERT_TRUE(solver.factorize(spd).isOk());
fesa::Vector solution{2U};
ASSERT_TRUE(solver.solve(fesa::Vector{2U, 1.0}, solution).isOk());
EXPECT_NEAR(solution[0U], 2.0 / 11.0, 1.0e-14);
EXPECT_NEAR(solution[1U], 3.0 / 11.0, 1.0e-14);
// Refactorization from the trivial state must establish ordinary PARDISO
// state rather than retaining a zero-equation shortcut.
const auto spd = MakeDenseCsr(2U, 2U, {4.0, 1.0, 1.0, 3.0});
ASSERT_TRUE(solver.Factorize(spd).IsOk());
fesa::Vector solution{2U};
ASSERT_TRUE(solver.Solve(fesa::Vector{2U, 1.0}, solution).IsOk());
EXPECT_NEAR(solution[0U], 2.0 / 11.0, 1.0e-14);
EXPECT_NEAR(solution[1U], 3.0 / 11.0, 1.0e-14);
const double solvedFirst = solution[0U];
const double solvedSecond = solution[1U];
expectSolverFailure(solver.solve(fesa::Vector{1U, 1.0}, solution));
EXPECT_DOUBLE_EQ(solution[0U], solvedFirst);
EXPECT_DOUBLE_EQ(solution[1U], solvedSecond);
const double solved_first = solution[0U];
const double solved_second = solution[1U];
ExpectSolverFailure(solver.Solve(fesa::Vector{1U, 1.0}, solution));
EXPECT_DOUBLE_EQ(solution[0U], solved_first);
EXPECT_DOUBLE_EQ(solution[1U], solved_second);
fesa::Vector wrongSolution{1U};
wrongSolution[0U] = 41.0;
expectSolverFailure(solver.solve(fesa::Vector{2U, 1.0}, wrongSolution));
EXPECT_DOUBLE_EQ(wrongSolution[0U], 41.0);
fesa::Vector wrong_solution{1U};
wrong_solution[0U] = 41.0;
ExpectSolverFailure(solver.Solve(fesa::Vector{2U, 1.0}, wrong_solution));
EXPECT_DOUBLE_EQ(wrong_solution[0U], 41.0);
const auto rectangular = makeDenseCsr(
2U, 3U, {2.0, 0.0, 0.0, 0.0, 3.0, 0.0});
const auto rectangularStatus = solver.factorize(rectangular);
expectSolverFailure(rectangularStatus);
EXPECT_EQ(
rectangularStatus.diagnostics().front().code,
"solver-matrix-not-square");
const auto rectangular = MakeDenseCsr(2U, 3U, {2.0, 0.0, 0.0, 0.0, 3.0, 0.0});
const auto rectangular_status = solver.Factorize(rectangular);
ExpectSolverFailure(rectangular_status);
EXPECT_EQ(rectangular_status.Diagnostics().front().code,
"solver-matrix-not-square");
fesa::SparsePattern invalidPattern{{0U, 2U}, {0U}};
auto invalidCsr = fesa::SparseMatrix::fromCoo(
1U,
1U,
{{0U, 0U, 1.0, 0U, 0U}},
invalidPattern);
EXPECT_FALSE(invalidCsr.hasValue());
fesa::SparsePattern invalid_pattern{{0U, 2U}, {0U}};
auto invalid_csr = fesa::SparseMatrix::FromCoo(
1U, 1U, {{0U, 0U, 1.0, 0U, 0U}}, invalid_pattern);
EXPECT_FALSE(invalid_csr.HasValue());
const auto nonsymmetric = makeDenseCsr(2U, 2U, {2.0, 1.0, 0.0, 3.0});
const auto nonsymmetricStatus = solver.factorize(nonsymmetric);
expectSolverFailure(nonsymmetricStatus);
EXPECT_EQ(
nonsymmetricStatus.diagnostics().front().code,
"solver-matrix-not-symmetric");
const auto nonsymmetric = MakeDenseCsr(2U, 2U, {2.0, 1.0, 0.0, 3.0});
const auto nonsymmetric_status = solver.Factorize(nonsymmetric);
ExpectSolverFailure(nonsymmetric_status);
EXPECT_EQ(nonsymmetric_status.Diagnostics().front().code,
"solver-matrix-not-symmetric");
const auto scaledNonsymmetric = makeDenseCsr(
2U, 2U, {2.0e-20, 1.0e-20, 1.1e-20, 3.0e-20});
const auto scaledNonsymmetricStatus =
solver.factorize(scaledNonsymmetric);
// Stop this case before inspecting diagnostics when the production code
// incorrectly accepts the matrix; this keeps the RED failure deterministic.
ASSERT_FALSE(scaledNonsymmetricStatus.isOk());
expectSolverFailure(scaledNonsymmetricStatus);
EXPECT_EQ(
scaledNonsymmetricStatus.diagnostics().front().code,
"solver-matrix-not-symmetric");
fesa::SparsePattern noDiagonalPattern{{0U, 1U, 2U}, {1U, 0U}};
auto noDiagonal = fesa::SparseMatrix::fromCoo(
2U,
2U,
{{0U, 1U, 1.0, 0U, 0U}, {1U, 0U, 1.0, 1U, 0U}},
noDiagonalPattern);
ASSERT_TRUE(noDiagonal.hasValue());
const auto noDiagonalStatus = solver.factorize(noDiagonal.value());
expectSolverFailure(noDiagonalStatus);
EXPECT_EQ(
noDiagonalStatus.diagnostics().front().code,
"solver-missing-diagonal");
const auto scaled_nonsymmetric =
MakeDenseCsr(2U, 2U, {2.0e-20, 1.0e-20, 1.1e-20, 3.0e-20});
const auto scaled_nonsymmetric_status = solver.Factorize(scaled_nonsymmetric);
// Stop this case before inspecting diagnostics when the production code
// incorrectly accepts the matrix; this keeps the RED failure deterministic.
ASSERT_FALSE(scaled_nonsymmetric_status.IsOk());
ExpectSolverFailure(scaled_nonsymmetric_status);
EXPECT_EQ(scaled_nonsymmetric_status.Diagnostics().front().code,
"solver-matrix-not-symmetric");
fesa::SparsePattern no_diagonal_pattern{{0U, 1U, 2U}, {1U, 0U}};
auto no_diagonal = fesa::SparseMatrix::FromCoo(
2U, 2U, {{0U, 1U, 1.0, 0U, 0U}, {1U, 0U, 1.0, 1U, 0U}},
no_diagonal_pattern);
ASSERT_TRUE(no_diagonal.HasValue());
const auto no_diagonal_status = solver.Factorize(no_diagonal.Value());
ExpectSolverFailure(no_diagonal_status);
EXPECT_EQ(no_diagonal_status.Diagnostics().front().code,
"solver-missing-diagonal");
}
@@ -1,7 +1,4 @@
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include <gtest/gtest.h>
@@ -12,261 +9,242 @@
#include <utility>
#include <vector>
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.h"
namespace {
fesa::SparseMatrix makeDenseCsr(
const std::size_t size,
const std::vector<double>& values) {
EXPECT_EQ(values.size(), size * size);
fesa::SparseMatrix MakeDenseCsr(const std::size_t size,
const std::vector<double>& values) {
EXPECT_EQ(values.size(), size * size);
fesa::SparsePattern pattern;
std::vector<fesa::CooContribution> contributions;
pattern.rowOffsets.reserve(size + 1U);
pattern.rowOffsets.push_back(0U);
for (std::size_t row = 0U; row < size; ++row) {
for (std::size_t column = 0U; column < size; ++column) {
pattern.columnIndices.push_back(column);
contributions.push_back({
row,
column,
values[row * size + column],
row,
column});
}
pattern.rowOffsets.push_back(pattern.columnIndices.size());
fesa::SparsePattern pattern;
std::vector<fesa::CooContribution> contributions;
pattern.rowOffsets.reserve(size + 1U);
pattern.rowOffsets.push_back(0U);
for (std::size_t row = 0U; row < size; ++row) {
for (std::size_t column = 0U; column < size; ++column) {
pattern.columnIndices.push_back(column);
contributions.push_back(
{row, column, values[row * size + column], row, column});
}
pattern.rowOffsets.push_back(pattern.columnIndices.size());
}
auto matrix = fesa::SparseMatrix::fromCoo(
size, size, std::move(contributions), pattern);
EXPECT_TRUE(matrix.hasValue());
return std::move(matrix.value());
auto matrix = fesa::SparseMatrix::FromCoo(size, size,
std::move(contributions), pattern);
EXPECT_TRUE(matrix.HasValue());
return std::move(matrix.Value());
}
fesa::Vector makeVector(const std::initializer_list<double> values) {
fesa::Vector result{values.size()};
std::size_t index = 0U;
for (const double value : values) {
result[index++] = value;
}
return result;
fesa::Vector MakeVector(const std::initializer_list<double> values) {
fesa::Vector result{values.size()};
std::size_t index = 0U;
for (const double value : values) {
result[index++] = value;
}
return result;
}
double normalizedResidual(
const fesa::SparseMatrix& matrix,
const fesa::Vector& solution,
const fesa::Vector& rhs) {
auto residual = matrix.multiply(solution);
residual.axpy(-1.0, rhs);
const double numerator = residual.norm();
const double denominator = rhs.norm();
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
return (std::numeric_limits<double>::infinity)();
}
if (denominator == 0.0) {
return numerator == 0.0 ? 0.0 :
(std::numeric_limits<double>::infinity)();
}
return numerator / denominator;
double NormalizedResidual(const fesa::SparseMatrix& matrix,
const fesa::Vector& solution,
const fesa::Vector& rhs) {
auto residual = matrix.Multiply(solution);
residual.Axpy(-1.0, rhs);
const double numerator = residual.Norm();
const double denominator = rhs.Norm();
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
return (std::numeric_limits<double>::infinity)();
}
if (denominator == 0.0) {
return numerator == 0.0 ? 0.0 : (std::numeric_limits<double>::infinity)();
}
return numerator / denominator;
}
double relativeError(
const fesa::Vector& actual,
const fesa::Vector& expected) {
auto difference = actual;
difference.axpy(-1.0, expected);
const double numerator = difference.norm();
const double denominator = expected.norm();
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
return (std::numeric_limits<double>::infinity)();
}
if (denominator == 0.0) {
return numerator == 0.0 ? 0.0 :
(std::numeric_limits<double>::infinity)();
}
return numerator / denominator;
double RelativeError(const fesa::Vector& actual, const fesa::Vector& expected) {
auto difference = actual;
difference.Axpy(-1.0, expected);
const double numerator = difference.Norm();
const double denominator = expected.Norm();
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
return (std::numeric_limits<double>::infinity)();
}
if (denominator == 0.0) {
return numerator == 0.0 ? 0.0 : (std::numeric_limits<double>::infinity)();
}
return numerator / denominator;
}
void expectStructuredSolverFailure(const fesa::Status& status) {
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::solver);
ASSERT_EQ(status.diagnostics().size(), 1U);
EXPECT_EQ(status.diagnostics()[0U].severity, fesa::Severity::error);
EXPECT_FALSE(status.diagnostics()[0U].code.empty());
EXPECT_FALSE(status.diagnostics()[0U].message.empty());
void ExpectStructuredSolverFailure(const fesa::Status& status) {
EXPECT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kSolver);
ASSERT_EQ(status.Diagnostics().size(), 1U);
EXPECT_EQ(status.Diagnostics()[0U].severity, fesa::Severity::kError);
EXPECT_FALSE(status.Diagnostics()[0U].code.empty());
EXPECT_FALSE(status.Diagnostics()[0U].message.empty());
}
} // namespace
} // namespace
TEST(MklPardisoSolver, SolvesKnownSpdWithNormalizedResidual) {
const auto matrix = makeDenseCsr(3U, {
6.0, 2.0, 1.0,
2.0, 5.0, 2.0,
1.0, 2.0, 4.0});
const auto expected = makeVector({1.0, -2.0, 3.0});
const auto rhs = matrix.multiply(expected);
const auto matrix =
MakeDenseCsr(3U, {6.0, 2.0, 1.0, 2.0, 5.0, 2.0, 1.0, 2.0, 4.0});
const auto expected = MakeVector({1.0, -2.0, 3.0});
const auto rhs = matrix.Multiply(expected);
fesa::MklPardisoSolver concreteSolver;
fesa::LinearSolver& solver = concreteSolver;
ASSERT_TRUE(solver.factorize(matrix).isOk());
fesa::MklPardisoSolver concrete_solver;
fesa::LinearSolver& solver = concrete_solver;
ASSERT_TRUE(solver.Factorize(matrix).IsOk());
fesa::Vector solution{3U};
ASSERT_TRUE(solver.solve(rhs, solution).isOk());
EXPECT_LE(normalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(relativeError(solution, expected), 1.0e-9);
fesa::Vector solution{3U};
ASSERT_TRUE(solver.Solve(rhs, solution).IsOk());
EXPECT_LE(NormalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(RelativeError(solution, expected), 1.0e-9);
}
TEST(MklPardisoSolver, ReusesOneFactorizationForRepeatedRhs) {
const auto matrix = makeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
const auto expectedFirst = makeVector({1.0, 2.0});
const auto expectedSecond = makeVector({-2.0, 0.5});
const auto rhsFirst = matrix.multiply(expectedFirst);
const auto rhsSecond = matrix.multiply(expectedSecond);
const auto matrix = MakeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
const auto expected_first = MakeVector({1.0, 2.0});
const auto expected_second = MakeVector({-2.0, 0.5});
const auto rhs_first = matrix.Multiply(expected_first);
const auto rhs_second = matrix.Multiply(expected_second);
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.factorize(matrix).isOk());
fesa::Vector first{2U};
fesa::Vector second{2U};
ASSERT_TRUE(solver.solve(rhsFirst, first).isOk());
ASSERT_TRUE(solver.solve(rhsSecond, second).isOk());
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.Factorize(matrix).IsOk());
fesa::Vector first{2U};
fesa::Vector second{2U};
ASSERT_TRUE(solver.Solve(rhs_first, first).IsOk());
ASSERT_TRUE(solver.Solve(rhs_second, second).IsOk());
EXPECT_LE(relativeError(first, expectedFirst), 1.0e-9);
EXPECT_LE(relativeError(second, expectedSecond), 1.0e-9);
EXPECT_LE(normalizedResidual(matrix, first, rhsFirst), 1.0e-10);
EXPECT_LE(normalizedResidual(matrix, second, rhsSecond), 1.0e-10);
EXPECT_LE(RelativeError(first, expected_first), 1.0e-9);
EXPECT_LE(RelativeError(second, expected_second), 1.0e-9);
EXPECT_LE(NormalizedResidual(matrix, first, rhs_first), 1.0e-10);
EXPECT_LE(NormalizedResidual(matrix, second, rhs_second), 1.0e-10);
}
TEST(MklPardisoSolver, RefactorizesWithoutLeakingState) {
const auto firstMatrix = makeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
const auto secondMatrix = makeDenseCsr(2U, {2.0, 0.0, 0.0, 5.0});
const auto firstExpected = makeVector({1.0, 2.0});
const auto secondExpected = makeVector({-3.0, 4.0});
const auto first_matrix = MakeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
const auto second_matrix = MakeDenseCsr(2U, {2.0, 0.0, 0.0, 5.0});
const auto first_expected = MakeVector({1.0, 2.0});
const auto second_expected = MakeVector({-3.0, 4.0});
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.factorize(firstMatrix).isOk());
fesa::Vector firstSolution{2U};
ASSERT_TRUE(
solver.solve(firstMatrix.multiply(firstExpected), firstSolution).isOk());
EXPECT_LE(relativeError(firstSolution, firstExpected), 1.0e-9);
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.Factorize(first_matrix).IsOk());
fesa::Vector first_solution{2U};
ASSERT_TRUE(
solver.Solve(first_matrix.Multiply(first_expected), first_solution)
.IsOk());
EXPECT_LE(RelativeError(first_solution, first_expected), 1.0e-9);
ASSERT_TRUE(solver.factorize(secondMatrix).isOk());
fesa::Vector secondSolution{2U};
const auto secondRhs = secondMatrix.multiply(secondExpected);
ASSERT_TRUE(solver.solve(secondRhs, secondSolution).isOk());
EXPECT_LE(relativeError(secondSolution, secondExpected), 1.0e-9);
EXPECT_LE(
normalizedResidual(secondMatrix, secondSolution, secondRhs), 1.0e-10);
ASSERT_TRUE(solver.Factorize(second_matrix).IsOk());
fesa::Vector second_solution{2U};
const auto second_rhs = second_matrix.Multiply(second_expected);
ASSERT_TRUE(solver.Solve(second_rhs, second_solution).IsOk());
EXPECT_LE(RelativeError(second_solution, second_expected), 1.0e-9);
EXPECT_LE(NormalizedResidual(second_matrix, second_solution, second_rhs),
1.0e-10);
}
TEST(MklPardisoSolver, ClassifiesSingularIndefiniteAndNonfiniteFailures) {
fesa::MklPardisoSolver solver;
const auto singular = MakeDenseCsr(2U, {1.0, 1.0, 1.0, 1.0});
const auto singular_status = solver.Factorize(singular);
ExpectStructuredSolverFailure(singular_status);
EXPECT_TRUE(singular_status.Diagnostics()[0U].code ==
"pardiso-zero-or-negative-pivot" ||
singular_status.Diagnostics()[0U].code ==
"pardiso-singular-diagonal");
EXPECT_NE(
singular_status.Diagnostics()[0U].entity_identity.find("phase=22,error="),
std::string::npos);
const auto indefinite = MakeDenseCsr(2U, {1.0, 2.0, 2.0, 1.0});
const auto indefinite_status = solver.Factorize(indefinite);
ExpectStructuredSolverFailure(indefinite_status);
EXPECT_TRUE(indefinite_status.Diagnostics()[0U].code ==
"pardiso-zero-or-negative-pivot" ||
indefinite_status.Diagnostics()[0U].code ==
"pardiso-singular-diagonal");
EXPECT_NE(indefinite_status.Diagnostics()[0U].entity_identity.find(
"phase=22,error="),
std::string::npos);
const auto spd = MakeDenseCsr(2U, {3.0, 1.0, 1.0, 2.0});
ASSERT_TRUE(solver.Factorize(spd).IsOk());
auto rhs = MakeVector({1.0, 2.0});
rhs[1U] = (std::numeric_limits<double>::infinity)();
fesa::Vector solution{2U};
solution[0U] = 23.0;
solution[1U] = -9.0;
const auto rhs_status = solver.Solve(rhs, solution);
ExpectStructuredSolverFailure(rhs_status);
EXPECT_EQ(rhs_status.Diagnostics()[0U].code, "nonfinite-solver-rhs");
EXPECT_DOUBLE_EQ(solution[0U], 23.0);
EXPECT_DOUBLE_EQ(solution[1U], -9.0);
fesa::SparsePattern pattern{{0U, 1U}, {0U}};
auto nonfinite_matrix = fesa::SparseMatrix::FromCoo(
1U, 1U, {{0U, 0U, (std::numeric_limits<double>::quiet_NaN)(), 0U, 0U}},
pattern);
EXPECT_FALSE(nonfinite_matrix.HasValue());
EXPECT_EQ(nonfinite_matrix.GetStatus().Diagnostics()[0U].code,
"nonfinite-sparse-value");
}
TEST(MklPardisoSolver,
ConditioningSweepPassesResolvedCasesAndFailsUnresolvedCasesExplicitly) {
const std::vector<double> common_scales{1.0e-12, 1.0, 1.0e12};
for (const double scale : common_scales) {
const auto matrix =
MakeDenseCsr(2U, {4.0 * scale, 1.0 * scale, 1.0 * scale, 3.0 * scale});
const auto expected = MakeVector({1.25, -0.75});
const auto rhs = matrix.Multiply(expected);
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.Factorize(matrix).IsOk()) << "scale=" << scale;
fesa::Vector solution{2U};
ASSERT_TRUE(solver.Solve(rhs, solution).IsOk()) << "scale=" << scale;
EXPECT_LE(NormalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(RelativeError(solution, expected), 1.0e-9);
}
const double resolved_ratio = 1.0e-8;
const auto resolved_matrix =
MakeDenseCsr(2U, {1.0, 0.0, 0.0, resolved_ratio});
const auto resolved_expected = MakeVector({0.5, -2.0});
const auto resolved_rhs = resolved_matrix.Multiply(resolved_expected);
fesa::MklPardisoSolver resolved_solver;
ASSERT_TRUE(resolved_solver.Factorize(resolved_matrix).IsOk());
fesa::Vector resolved_solution{2U};
ASSERT_TRUE(resolved_solver.Solve(resolved_rhs, resolved_solution).IsOk());
EXPECT_LE(
NormalizedResidual(resolved_matrix, resolved_solution, resolved_rhs),
1.0e-10);
EXPECT_LE(RelativeError(resolved_solution, resolved_expected), 1.0e-9);
const std::vector<double> unresolved_candidates{1.0e-16, 1.0e-300};
for (const double ratio : unresolved_candidates) {
const auto matrix = MakeDenseCsr(2U, {1.0, 0.0, 0.0, ratio});
const auto expected = MakeVector({0.5, -2.0});
const auto rhs = matrix.Multiply(expected);
fesa::MklPardisoSolver solver;
const auto singular = makeDenseCsr(2U, {1.0, 1.0, 1.0, 1.0});
const auto singularStatus = solver.factorize(singular);
expectStructuredSolverFailure(singularStatus);
EXPECT_TRUE(
singularStatus.diagnostics()[0U].code ==
"pardiso-zero-or-negative-pivot" ||
singularStatus.diagnostics()[0U].code ==
"pardiso-singular-diagonal");
EXPECT_NE(
singularStatus.diagnostics()[0U].entityIdentity.find(
"phase=22,error="),
std::string::npos);
const auto factor_status = solver.Factorize(matrix);
if (!factor_status.IsOk()) {
ExpectStructuredSolverFailure(factor_status);
continue;
}
const auto indefinite = makeDenseCsr(2U, {1.0, 2.0, 2.0, 1.0});
const auto indefiniteStatus = solver.factorize(indefinite);
expectStructuredSolverFailure(indefiniteStatus);
EXPECT_TRUE(
indefiniteStatus.diagnostics()[0U].code ==
"pardiso-zero-or-negative-pivot" ||
indefiniteStatus.diagnostics()[0U].code ==
"pardiso-singular-diagonal");
EXPECT_NE(
indefiniteStatus.diagnostics()[0U].entityIdentity.find(
"phase=22,error="),
std::string::npos);
const auto spd = makeDenseCsr(2U, {3.0, 1.0, 1.0, 2.0});
ASSERT_TRUE(solver.factorize(spd).isOk());
auto rhs = makeVector({1.0, 2.0});
rhs[1U] = (std::numeric_limits<double>::infinity)();
fesa::Vector solution{2U};
solution[0U] = 23.0;
solution[1U] = -9.0;
const auto rhsStatus = solver.solve(rhs, solution);
expectStructuredSolverFailure(rhsStatus);
EXPECT_EQ(rhsStatus.diagnostics()[0U].code, "nonfinite-solver-rhs");
EXPECT_DOUBLE_EQ(solution[0U], 23.0);
EXPECT_DOUBLE_EQ(solution[1U], -9.0);
fesa::SparsePattern pattern{{0U, 1U}, {0U}};
auto nonfiniteMatrix = fesa::SparseMatrix::fromCoo(
1U,
1U,
{{0U,
0U,
(std::numeric_limits<double>::quiet_NaN)(),
0U,
0U}},
pattern);
EXPECT_FALSE(nonfiniteMatrix.hasValue());
EXPECT_EQ(
nonfiniteMatrix.status().diagnostics()[0U].code,
"nonfinite-sparse-value");
}
TEST(MklPardisoSolver, ConditioningSweepPassesResolvedCasesAndFailsUnresolvedCasesExplicitly) {
const std::vector<double> commonScales{1.0e-12, 1.0, 1.0e12};
for (const double scale : commonScales) {
const auto matrix = makeDenseCsr(2U, {
4.0 * scale, 1.0 * scale,
1.0 * scale, 3.0 * scale});
const auto expected = makeVector({1.25, -0.75});
const auto rhs = matrix.multiply(expected);
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.factorize(matrix).isOk()) << "scale=" << scale;
fesa::Vector solution{2U};
ASSERT_TRUE(solver.solve(rhs, solution).isOk()) << "scale=" << scale;
EXPECT_LE(normalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(relativeError(solution, expected), 1.0e-9);
const auto solve_status = solver.Solve(rhs, solution);
if (!solve_status.IsOk()) {
ExpectStructuredSolverFailure(solve_status);
continue;
}
const double resolvedRatio = 1.0e-8;
const auto resolvedMatrix = makeDenseCsr(
2U, {1.0, 0.0, 0.0, resolvedRatio});
const auto resolvedExpected = makeVector({0.5, -2.0});
const auto resolvedRhs = resolvedMatrix.multiply(resolvedExpected);
fesa::MklPardisoSolver resolvedSolver;
ASSERT_TRUE(resolvedSolver.factorize(resolvedMatrix).isOk());
fesa::Vector resolvedSolution{2U};
ASSERT_TRUE(resolvedSolver.solve(resolvedRhs, resolvedSolution).isOk());
EXPECT_LE(
normalizedResidual(resolvedMatrix, resolvedSolution, resolvedRhs),
1.0e-10);
EXPECT_LE(relativeError(resolvedSolution, resolvedExpected), 1.0e-9);
const std::vector<double> unresolvedCandidates{1.0e-16, 1.0e-300};
for (const double ratio : unresolvedCandidates) {
const auto matrix = makeDenseCsr(2U, {1.0, 0.0, 0.0, ratio});
const auto expected = makeVector({0.5, -2.0});
const auto rhs = matrix.multiply(expected);
fesa::MklPardisoSolver solver;
const auto factorStatus = solver.factorize(matrix);
if (!factorStatus.isOk()) {
expectStructuredSolverFailure(factorStatus);
continue;
}
fesa::Vector solution{2U};
const auto solveStatus = solver.solve(rhs, solution);
if (!solveStatus.isOk()) {
expectStructuredSolverFailure(solveStatus);
continue;
}
EXPECT_LE(normalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(relativeError(solution, expected), 1.0e-9);
}
EXPECT_LE(NormalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(RelativeError(solution, expected), 1.0e-9);
}
}